v183 - reset_again

This commit is contained in:
Arseny Sazhin (via WeWeb) 2025-09-08 18:37:38 +03:00
parent fd158e79aa
commit 2de028e570
56 changed files with 1137 additions and 177 deletions

View File

@ -1 +1 @@
{"name":"weweb-front","version":"4.0.0","private":true,"type":"module","scripts":{"serve":"vite","build":"vite build","postbuild":"node ./postbuild.js"},"dependencies":{"@vueuse/head":"2.0.0","axios":"1.9.0","html-escaper":"3.0.3","lodash":"4.17.21","pinia":"3.0.2","short-unique-id":"5.3.2","tiny-emitter":"2.1.0","uuid":"11.1.0","vue":"3.5.13","vue-cookie-next":"1.3.0","vue-meta":"2.4.0","vue-router":"4.5.1","vuex":"4.1.0","dayjs":"1.11.0","@supabase/supabase-js":"2.50.3","qs":"^6.14.0","@vueform/multiselect":"2.6.2","vue-ellipse-progress":"^2.1.1","@vueuse/core":"^13.0.0","diff":"^5.1.0","animejs":"^3.2.2","marked":"^14.1.2","medium-zoom":"^1.0.6","prismjs":"^1.23.0","@tiptap/extension-color":"2.8.0","@tiptap/extension-image":"2.8.0","@tiptap/extension-link":"2.8.0","@tiptap/extension-mathematics":"2.26.1","@tiptap/extension-mention":"2.8.0","@tiptap/extension-placeholder":"2.8.0","@tiptap/extension-table":"2.8.0","@tiptap/extension-table-cell":"2.8.0","@tiptap/extension-table-header":"2.8.0","@tiptap/extension-table-row":"2.8.0","@tiptap/extension-task-item":"2.8.0","@tiptap/extension-task-list":"2.8.0","@tiptap/extension-text-align":"2.8.0","@tiptap/extension-text-style":"2.8.0","@tiptap/extension-underline":"2.8.0","@tiptap/pm":"2.8.0","@tiptap/starter-kit":"2.8.0","@tiptap/suggestion":"2.8.0","@tiptap/vue-3":"2.8.0","katex":"^0.16.22","prosemirror-commands":"1.5.0","prosemirror-dropcursor":"1.5.0","prosemirror-gapcursor":"1.3.1","prosemirror-history":"1.3.0","prosemirror-keymap":"1.2.0","prosemirror-schema-list":"1.2.2","tiptap-markdown":"^0.8.10","lodash-es":"^4.17.21","@vuepic/vue-datepicker":"3.6.8","date-fns":"4.1.0"},"devDependencies":{"@vitejs/plugin-vue":"5.2.4","autoprefixer":"10.4.21","handlebars":"4.7.8","sass-embedded":"1.89.0","vite":"6.3.5","vite-plugin-node-polyfills":"0.23.0"},"browserslist":["last 3 years"]}
{"name":"weweb-front","version":"4.0.0","private":true,"type":"module","scripts":{"serve":"vite","build":"vite build","postbuild":"node ./postbuild.js"},"dependencies":{"@vueuse/head":"2.0.0","axios":"1.9.0","html-escaper":"3.0.3","lodash":"4.17.21","pinia":"3.0.2","short-unique-id":"5.3.2","tiny-emitter":"2.1.0","uuid":"11.1.0","vue":"3.5.13","vue-cookie-next":"1.3.0","vue-meta":"2.4.0","vue-router":"4.5.1","vuex":"4.1.0","dayjs":"1.11.0","@supabase/supabase-js":"2.50.3","qs":"^6.14.0","@vueform/multiselect":"2.6.2","vue-ellipse-progress":"^2.1.1","@vueuse/core":"^13.0.0","diff":"^5.1.0","animejs":"^3.2.2","marked":"^14.1.2","medium-zoom":"^1.0.6","prismjs":"^1.23.0","lodash-es":"^4.17.21","@vuepic/vue-datepicker":"3.6.8","date-fns":"4.1.0","@tiptap/extension-color":"2.8.0","@tiptap/extension-image":"2.8.0","@tiptap/extension-link":"2.8.0","@tiptap/extension-mathematics":"2.26.1","@tiptap/extension-mention":"2.8.0","@tiptap/extension-placeholder":"2.8.0","@tiptap/extension-table":"2.8.0","@tiptap/extension-table-cell":"2.8.0","@tiptap/extension-table-header":"2.8.0","@tiptap/extension-table-row":"2.8.0","@tiptap/extension-task-item":"2.8.0","@tiptap/extension-task-list":"2.8.0","@tiptap/extension-text-align":"2.8.0","@tiptap/extension-text-style":"2.8.0","@tiptap/extension-underline":"2.8.0","@tiptap/pm":"2.8.0","@tiptap/starter-kit":"2.8.0","@tiptap/suggestion":"2.8.0","@tiptap/vue-3":"2.8.0","katex":"^0.16.22","prosemirror-commands":"1.5.0","prosemirror-dropcursor":"1.5.0","prosemirror-gapcursor":"1.3.1","prosemirror-history":"1.3.0","prosemirror-keymap":"1.2.0","prosemirror-schema-list":"1.2.2","tiptap-markdown":"^0.8.10"},"devDependencies":{"@vitejs/plugin-vue":"5.2.4","autoprefixer":"10.4.21","handlebars":"4.7.8","sass-embedded":"1.89.0","vite":"6.3.5","vite-plugin-node-polyfills":"0.23.0"},"browserslist":["last 3 years"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"cacheVersion":182,"page":{"id":"cf9f551f-e733-4934-a682-535575cb7c70","paths":{"en":"promo","default":"promo"},"cmsDataSetPath":null,"workflows":[]},"sections":{"4a2188f6-d98c-4150-9ba7-5918c72f3421":{"uid":"4a2188f6-d98c-4150-9ba7-5918c72f3421","linkId":"416695e7-e54e-4897-9f9a-befeb223f1b0","_state":{"style":{"default":{}}},"content":{"default":{"wwObjects":[],"_ww-layout_alignItems":"flex-start","_ww-layout_flexDirection":"column"}},"sectionBaseId":"99586bd3-2b15-4d6b-a025-6a50d07ca845","sectionTitle":"Section"}},"wwObjects":{},"collections":[],"variables":[],"workflows":[],"formulas":[],"libraryComponents":[]}
{"cacheVersion":183,"page":{"id":"cf9f551f-e733-4934-a682-535575cb7c70","paths":{"en":"promo","default":"promo"},"cmsDataSetPath":null,"workflows":[]},"sections":{"4a2188f6-d98c-4150-9ba7-5918c72f3421":{"uid":"4a2188f6-d98c-4150-9ba7-5918c72f3421","linkId":"416695e7-e54e-4897-9f9a-befeb223f1b0","_state":{"style":{"default":{}}},"content":{"default":{"wwObjects":[],"_ww-layout_alignItems":"flex-start","_ww-layout_flexDirection":"column"}},"sectionBaseId":"99586bd3-2b15-4d6b-a025-6a50d07ca845","sectionTitle":"Section"}},"wwObjects":{},"collections":[],"variables":[],"workflows":[],"formulas":[],"libraryComponents":[]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
</svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@ -1 +1 @@
{"name":"Образовательная платформа Meetguru","short_name":"Образовательная платформа Meetguru","icons":[{"src":"images/48-favicon.png?_wwcv=182","type":"image/png","sizes":"48x48"},{"src":"images/72-favicon.png?_wwcv=182","type":"image/png","sizes":"72x72"},{"src":"images/96-favicon.png?_wwcv=182","type":"image/png","sizes":"96x96"},{"src":"images/128-favicon.png?_wwcv=182","type":"image/png","sizes":"128x128"},{"src":"images/144-favicon.png?_wwcv=182","type":"image/png","sizes":"144x144"},{"src":"images/152-favicon.png?_wwcv=182","type":"image/png","sizes":"152x152"},{"src":"images/192-favicon.png?_wwcv=182","type":"image/png","sizes":"192x192"},{"src":"images/256-favicon.png?_wwcv=182","type":"image/png","sizes":"256x256"},{"src":"images/384-favicon.png?_wwcv=182","type":"image/png","sizes":"384x384"},{"src":"images/512-favicon.png?_wwcv=182","type":"image/png","sizes":"512x512"}],"start_url":"/","display":"fullscreen","scope":"/","background_color":"#FFFFFF","theme_color":"#FFFFFF"}
{"name":"Образовательная платформа Meetguru","short_name":"Образовательная платформа Meetguru","icons":[{"src":"images/48-favicon.png?_wwcv=183","type":"image/png","sizes":"48x48"},{"src":"images/72-favicon.png?_wwcv=183","type":"image/png","sizes":"72x72"},{"src":"images/96-favicon.png?_wwcv=183","type":"image/png","sizes":"96x96"},{"src":"images/128-favicon.png?_wwcv=183","type":"image/png","sizes":"128x128"},{"src":"images/144-favicon.png?_wwcv=183","type":"image/png","sizes":"144x144"},{"src":"images/152-favicon.png?_wwcv=183","type":"image/png","sizes":"152x152"},{"src":"images/192-favicon.png?_wwcv=183","type":"image/png","sizes":"192x192"},{"src":"images/256-favicon.png?_wwcv=183","type":"image/png","sizes":"256x256"},{"src":"images/384-favicon.png?_wwcv=183","type":"image/png","sizes":"384x384"},{"src":"images/512-favicon.png?_wwcv=183","type":"image/png","sizes":"512x512"}],"start_url":"/","display":"fullscreen","scope":"/","background_color":"#FFFFFF","theme_color":"#FFFFFF"}

View File

@ -1,4 +1,4 @@
const version = 182;
const version = 183;
self.addEventListener('install', event => {
// eslint-disable-next-line no-console
console.log(`Service worker v${version} installed`);

File diff suppressed because one or more lines are too long

View File

@ -38,3 +38,19 @@ export function getTargetMedia({ screenSize, elementState, isResponsive, allowSt
return targetMedia;
}
export function splitPath(fullPath) {
if (fullPath.startsWith('content.')) {
const parts = fullPath.replace('content.', '').split('.');
const media = parts[0];
const path = `content.${parts.slice(1).join('.')}`;
return { path, media };
} else if (fullPath.startsWith('_state.style.')) {
const parts = fullPath.replace('_state.style.', '').split('.');
const media = parts[0];
const path = `_state.style.${parts.slice(1).join('.')}`;
return { path, media };
} else {
return { path: fullPath, media: 'default' };
}
}

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,7 @@ import { textOptions } from './src/settings';
export default {
editor: {
deprecated: 'Use a rich text from the Elements panel instead of this one.',
label: {
en: 'Rich text',
},

View File

@ -33,14 +33,14 @@
:style="actionButtonStyles"
aria-label="Remove file"
>
<div class="fas fa-times"></div>
<div class="icon" v-html="removeIcon"></div>
</button>
</div>
</li>
</template>
<script>
import { computed, inject } from 'vue';
import { computed, inject, onMounted, ref } from 'vue';
export default {
props: {
@ -81,9 +81,12 @@ export default {
const showFileInfo = computed(() => content.value?.showFileInfo);
const fileItemStyles = computed(() => ({
padding: content.value?.fileItemPadding || '12px',
backgroundColor: content.value?.fileItemBackground || '#fff',
borderColor: content.value?.fileItemBorderColor || '#eee',
borderRadius: content.value?.fileItemBorderRadius || '6px',
padding: content.value?.fileItemPadding || '12px',
margin: content.value?.fileItemMargin || '0 0 8px 0',
boxShadow: content.value?.fileItemShadow || '0 2px 4px rgba(0, 0, 0, 0.05)',
position: 'relative',
overflow: 'hidden',
}));
@ -105,6 +108,7 @@ export default {
const actionButtonStyles = computed(() => ({
width: content.value?.actionButtonSize || '28px',
height: content.value?.actionButtonSize || '28px',
backgroundColor: content.value?.actionButtonBackground || '#fff',
color: content.value?.actionButtonColor || '#666',
borderRadius: content.value?.actionButtonBorderRadius || '4px',
margin: content.value?.actionButtonMargin || '0 0 0 4px',
@ -120,6 +124,17 @@ export default {
return `${(fileSizeInBytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
});
const { getIcon } = wwLib.useIcons();
const removeIcon = ref(null);
onMounted(async () => {
try {
removeIcon.value = await getIcon('lucide/trash');
} catch (e) {
removeIcon.value = null;
}
});
return {
formattedSize,
filesCount,
@ -129,6 +144,7 @@ export default {
actionButtonStyles,
content,
showFileInfo,
removeIcon,
};
},
};
@ -143,7 +159,6 @@ export default {
border-radius: v-bind('content?.fileItemBorderRadius || "6px"');
margin-bottom: v-bind('(content?.fileItemMargin || "0 0 8px 0").split(" ")[2] || "8px"');
background-color: v-bind('content?.fileItemBackground || "#fff"');
box-shadow: v-bind('content?.fileItemShadow || "0 2px 4px rgba(0, 0, 0, 0.05)"');
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
transform-origin: center;
position: relative;
@ -152,14 +167,6 @@ export default {
will-change: transform, opacity;
overflow: hidden;
&:hover {
border-color: v-bind('content?.fileItemHoverBorderColor || content?.fileItemBorderColor || "#ddd"');
background-color: v-bind('content?.fileItemHoverBackground || content?.fileItemBackground || "#fff"');
box-shadow: v-bind(
'content?.fileItemHoverShadow || content?.fileItemShadow || "0 2px 4px rgba(0, 0, 0, 0.05)"'
);
}
&__progress {
position: absolute;
top: 0;
@ -170,6 +177,14 @@ export default {
opacity: 0.2;
}
&:hover {
border-color: v-bind('content?.fileItemHoverBorderColor || content?.fileItemBorderColor || "#ddd"');
background-color: v-bind('content?.fileItemHoverBackground || content?.fileItemBackground || "#fff"');
box-shadow: v-bind(
'content?.fileItemHoverShadow || content?.fileItemShadow || "0 2px 4px rgba(0, 0, 0, 0.05)"'
);
}
&--disabled {
opacity: 0.6;
pointer-events: none;
@ -207,6 +222,10 @@ export default {
z-index: 1;
}
&:hover &__actions {
opacity: 1;
}
&__btn {
display: flex;
align-items: center;
@ -224,8 +243,8 @@ export default {
&:hover:not(:disabled) {
border-color: v-bind(
'content?.actionButtonHoverBorderColor || content?.fileItemHoverBorderColor || "#ddd"'
) !important;
background-color: v-bind('content?.actionButtonHoverBackground || "#f8f8f8"') !important;
);
background-color: v-bind('content?.actionButtonHoverBackground || "#f8f8f8"');
transform: scale(1.05);
}
@ -233,10 +252,25 @@ export default {
opacity: 0.5;
cursor: not-allowed;
}
}
&:hover &__actions {
opacity: 1;
&--remove:hover:not(:disabled) {
color: v-bind('content?.actionButtonRemoveHoverColor || content?.progressBarColor || "#999"');
border-color: v-bind('content?.actionButtonRemoveHoverColor || content?.progressBarColor || "#999"');
}
.icon {
width: 50%;
height: 50%;
display: flex;
align-items: center;
justify-content: center;
:deep(svg) {
width: 100% !important;
height: 100% !important;
display: block;
}
}
}
}
</style>

View File

@ -142,6 +142,7 @@ export default {
},
options: {
displayAllowedValues: ['flex', 'inline-flex', 'block'],
icons: ['lucide/trash'],
},
triggerEvents: [
{

View File

@ -138,7 +138,7 @@
v-if="dataType === 'multipart/form-data'"
tooltip-position="top-left"
forced-content="Not allowed with content-type multipart/form-data"
class="ml-2 text-yellow-500"
class="ml-2 content-warning"
/>
<wwEditorQuestionMark
v-else
@ -151,10 +151,34 @@
<wwEditorFormRow v-if="!isThroughServer">
<div class="flex items-center">
<wwEditorInputSwitch :model-value="isWithCredentials" @update:modelValue="setIsWithCredentials" />
<div class="body-2 ml-2">Send credentials</div>
<div class="body-sm ml-2">Send credentials</div>
<wwEditorQuestionMark tooltip-position="top-left" tooltip-name="rest-api-credentials" class="ml-auto" />
</div>
</wwEditorFormRow>
<wwEditorFormRow>
<div class="flex items-center">
<wwEditorInputSwitch :model-value="useStreaming" @update:modelValue="setUseStreaming" />
<div class="body-sm ml-2">Stream response</div>
<wwEditorQuestionMark
tooltip-position="top-left"
forced-content="The response will be streamed in real-time. You can use the stream variable to receive the data. This requires the server to support streaming responses."
class="ml-auto"
/>
</div>
</wwEditorFormRow>
<wwEditorInputRow
v-if="useStreaming"
label="Stream variable"
placeholder="Select an array variable"
type="select"
:actions="[{ icon: 'plus', label: 'Create variable', onAction: createWwVariable }]"
:options="wwVariableOptions"
:model-value="streamVariableId"
@update:modelValue="setStreamVariableId"
@action="action => action?.onAction()"
required
tooltip="The array variable that will receive the stream data"
/>
</template>
<script>
@ -172,10 +196,17 @@ export default {
dataType: null,
isThroughServer: false,
isWithCredentials: false,
useStreaming: false,
streamVariableId: null,
}),
},
},
emits: ['update:args'],
setup() {
const { website: websiteVariables, components: componentVariables } = wwLib.wwVariable.useEditorVariables();
return { websiteVariables, componentVariables };
},
data() {
return {
dataChoices: [
@ -230,9 +261,36 @@ export default {
useRawBody() {
return this.args.useRawBody || false;
},
useStreaming() {
return this.args.useStreaming || false;
},
streamVariableId() {
return this.args.streamVariableId;
},
isData() {
return ['POST', 'PUT', 'PATCH', 'DELETE'].includes(this.method);
},
wwVariables() {
return [
...(this.websiteVariables ? Object.values(this.websiteVariables) : []),
...(this.componentVariables ? Object.values(this.componentVariables) : []),
];
},
wwVariableOptions() {
return this.wwVariables
.filter(variable => variable.type === 'array')
.map(variable => {
const labelPrefix = variable.componentType
? wwLib.wwElement.getComponentLabel(variable.componentType, variable.componentUid)
: null;
const label = labelPrefix ? `${labelPrefix} - ${variable.name}` : variable.name;
return {
label,
value: variable.id,
icon: variable.type,
};
});
},
},
mounted() {
if (!this.method) this.setMethod('POST');
@ -270,6 +328,21 @@ export default {
setUseRawBody(useRawBody) {
this.$emit('update:args', { ...this.args, useRawBody, data: useRawBody ? null : [] });
},
setUseStreaming(useStreaming) {
this.$emit('update:args', {
...this.args,
useStreaming,
isThroughServer: useStreaming ? false : this.isThroughServer,
});
},
setStreamVariableId(streamVariableId) {
this.$emit('update:args', { ...this.args, streamVariableId });
},
createWwVariable() {
wwLib.wwPopupSidebars.open({ name: 'NAVIGATOR' });
wwLib.$emit('wwTopBar:navigator:tab', 'data');
wwLib.$emit('wwTopBar:navigator:data:variables:set', null);
},
},
};
</script>

View File

@ -0,0 +1,123 @@
export async function consumeEventStream(response, streamVariableId, wwUtils) {
if (!response.body) {
throw new Error('ReadableStream not supported in this browser.');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let streamActive = true;
while (streamActive) {
const { done, value } = await reader.read();
if (done) {
streamActive = false;
break;
}
const chunk = decoder.decode(value, { stream: true });
buffer += chunk;
let newlineIndex;
while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
let line = buffer.slice(0, newlineIndex).trim();
buffer = buffer.slice(newlineIndex + 1);
if (!line) continue;
if (line.startsWith('data:')) {
const dataContent = line.substring(5).trim();
if (dataContent === '[DONE]') {
streamActive = false;
wwUtils?.log('info', '[REST API Stream] Received [DONE] in data: line.');
break;
}
if (dataContent) {
try {
const parsedData = JSON.parse(dataContent);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, parsedData]);
} catch (parseError) {
wwUtils?.log('warn', `[REST API Stream] Non-JSON data in data: line: ${dataContent}`, {
parseError,
});
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, dataContent]);
}
}
} else if (line === '[DONE]') {
streamActive = false;
wwUtils?.log('info', '[REST API Stream] Received [DONE] on its own line.');
break;
} else if (
line.startsWith('id:') ||
line.startsWith('event:') ||
line.startsWith('retry:') ||
line.startsWith(':')
) {
wwUtils?.log('debug', `[REST API Stream] Ignoring SSE metadata line: ${line}`);
} else {
try {
const parsedData = JSON.parse(line);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, parsedData]);
} catch (parseError) {
wwUtils?.log('debug', `[REST API Stream] Adding raw line as non-JSON: ${line}`, {
parseError,
});
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, line]);
}
}
}
if (!streamActive) break;
}
const finalBufferTrimmed = buffer.trim();
if (finalBufferTrimmed) {
if (finalBufferTrimmed.startsWith('data:')) {
const dataContent = finalBufferTrimmed.substring(5).trim();
if (dataContent && dataContent !== '[DONE]') {
try {
const parsedData = JSON.parse(dataContent);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, parsedData]);
} catch (e) {
wwUtils?.log(
'warn',
`[REST API Stream] Failed to parse JSON from final buffer data: line: ${dataContent}`,
{ e }
);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, dataContent]);
}
} else if (dataContent === '[DONE]') {
wwUtils?.log('info', '[REST API Stream] Received [DONE] in final buffer data: line.');
}
} else if (finalBufferTrimmed === '[DONE]') {
wwUtils?.log('info', '[REST API Stream] Received [DONE] as final buffer content.');
} else if (
finalBufferTrimmed.startsWith('id:') ||
finalBufferTrimmed.startsWith('event:') ||
finalBufferTrimmed.startsWith('retry:') ||
finalBufferTrimmed.startsWith(':')
) {
wwUtils?.log('debug', `[REST API Stream] Ignoring SSE metadata in final buffer: ${finalBufferTrimmed}`);
} else {
try {
const parsedData = JSON.parse(finalBufferTrimmed);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, parsedData]);
} catch (e) {
wwUtils?.log('debug', `[REST API Stream] Adding raw final buffer as non-JSON: ${finalBufferTrimmed}`, {
e,
});
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, finalBufferTrimmed]);
}
}
}
return wwLib.wwVariable.getValue(streamVariableId);
}

View File

@ -1,4 +1,5 @@
import qs from 'qs';
import { consumeEventStream } from './utils/streamHandler';
export default {
/*=============================================m_ÔÔ_m=============================================\
@ -40,10 +41,29 @@ export default {
isThroughServer,
useRawBody = false,
isWithCredentials = false,
useStreaming = false,
streamVariableId = null,
},
wwUtils
) {
if (isThroughServer) {
if (useStreaming) {
if (isThroughServer) {
throw new Error('Streaming is not supported with server-side requests.');
}
return await this._streamApiRequest(
url,
method,
data,
headers,
params,
dataType,
useRawBody,
isWithCredentials,
streamVariableId,
wwUtils
);
} else if (isThroughServer) {
const websiteId = wwLib.wwWebsiteData.getInfo().id;
const pluginURL = wwLib.wwApiRequests._getPluginsUrl();
@ -74,46 +94,140 @@ export default {
return response.data;
},
async _streamApiRequest(
url,
method,
data,
headers,
params,
dataType,
useRawBody,
isWithCredentials,
streamVariableId,
wwUtils
) {
try {
wwLib.wwVariable.updateValue(streamVariableId, []);
const payload = computePayload(method, data, headers, params, dataType, useRawBody, wwUtils);
const streamHeaders = {
...payload.headers,
Accept: 'text/event-stream',
};
const response = await fetch(url, {
method,
headers: streamHeaders,
body: ['GET', 'HEAD'].includes(method) ? undefined : payload.data,
credentials: isWithCredentials ? 'include' : 'same-origin',
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP error ${response.status}: ${errorText}`);
}
return await consumeEventStream(response, streamVariableId, wwUtils);
} catch (error) {
wwUtils?.log('error', '[REST API Stream] Error', {
type: 'error',
preview: {
message: error.message,
stack: error.stack,
},
});
throw error;
}
},
};
function computePayload(method, data, headers, params, dataType, useRawBody) {
if (!useRawBody) {
data = computeList(data);
function computePayload(method, data, headers, params, dataType, useRawBody, wwUtils) {
try {
let processedData = data;
if (!useRawBody) {
processedData = computeList(data, 'data', wwUtils);
switch (dataType) {
case 'application/x-www-form-urlencoded': {
data = qs.stringify(data);
break;
}
case 'multipart/form-data': {
const formData = new FormData();
for (const key in data) formData.append(key, data[key]);
data = formData;
break;
switch (dataType) {
case 'application/x-www-form-urlencoded': {
processedData = qs.stringify(processedData);
break;
}
case 'multipart/form-data': {
const formData = new FormData();
for (const key in processedData) formData.append(key, processedData[key]);
processedData = formData;
break;
}
default:
break;
}
}
switch (method) {
case 'OPTIONS':
case 'GET':
case 'DELETE':
default:
break;
}
}
switch (method) {
case 'OPTIONS':
case 'GET':
case 'DELETE':
default:
break;
}
return {
data,
params: computeList(params),
headers: {
const processedParams = computeList(params, 'params', wwUtils);
const processedHeaders = {
'content-type': dataType || 'application/json',
...computeList(headers),
},
};
...computeList(headers, 'headers', wwUtils),
};
return {
data: processedData,
params: processedParams,
headers: processedHeaders,
};
} catch (error) {
wwUtils?.log('error', '[REST API] Error in computePayload', {
type: 'error',
preview: {
error: error.message,
stack: error.stack,
},
});
throw error;
}
}
function computeList(list) {
return (list || []).reduce((obj, item) => ({ ...obj, [item.key]: item.value }), {});
function computeList(list, label, wwUtils) {
try {
if (!list) return {};
if (!Array.isArray(list)) {
wwUtils?.log('warn', `[REST API] computeList expected array but got ${typeof list}`, {
type: 'warn',
preview: list,
});
return {};
}
const result = (list || []).reduce((obj, item) => {
if (!item || typeof item !== 'object' || !('key' in item)) {
wwUtils?.log('warn', `[REST API] computeList skipping invalid item`, {
type: 'warn',
preview: item,
});
return obj;
}
return { ...obj, [item.key]: item.value };
}, {});
return result;
} catch (error) {
wwUtils?.log('error', `[REST API] Error in computeList for ${label}`, {
type: 'error',
preview: {
error: error.message,
stack: error.stack,
list: typeof list === 'undefined' ? 'undefined' : list === null ? 'null' : list,
},
});
throw new Error(`Failed to process ${label}: ${error.message}`);
}
}

View File

@ -94,6 +94,30 @@
:model-value="body"
@update:modelValue="setArgs({ body: $event })"
/>
<wwEditorFormRow>
<div class="flex items-center">
<wwEditorInputSwitch :model-value="useStreaming" @update:modelValue="setUseStreaming" />
<div class="body-sm ml-2">Stream response</div>
<wwEditorQuestionMark
tooltip-position="top-left"
forced-content="The response will be streamed in real-time. You can use the stream variable to receive the data. This requires the edge function to support streaming responses."
class="ml-auto"
/>
</div>
</wwEditorFormRow>
<wwEditorInputRow
v-if="useStreaming"
label="Stream variable"
placeholder="Select an array variable"
type="select"
:actions="[{ icon: 'plus', label: 'Create variable', onAction: createWwVariable }]"
:options="wwVariableOptions"
:model-value="streamVariableId"
@update:modelValue="setArgs({ streamVariableId: $event })"
@action="action => action?.onAction()"
required
tooltip="The array variable that will receive the stream data"
/>
<wwLoader :loading="isLoading" />
</template>
@ -104,9 +128,14 @@ export default {
components: { Expandable },
props: {
plugin: { type: Object, required: true },
args: { type: Object, default: () => ({ fieldsMode: 'guided' }) },
args: { type: Object, default: () => ({ fieldsMode: 'guided', useStreaming: false }) },
},
emits: ['update:args'],
setup() {
const { website: websiteVariables, components: componentVariables } = wwLib.wwVariable.useEditorVariables();
return { websiteVariables, componentVariables };
},
data() {
return {
isAdvancedOpen: false,
@ -130,6 +159,35 @@ export default {
body() {
return this.args.body;
},
useStreaming() {
return this.args.useStreaming || false;
},
streamVariableId() {
return this.args.streamVariableId;
},
wwVariables() {
return [
...(this.websiteVariables ? Object.values(this.websiteVariables) : []),
...(this.componentVariables ? Object.values(this.componentVariables) : []),
];
},
wwVariableOptions() {
return this.wwVariables
.filter(variable => variable.type === 'array')
.map(variable => {
const labelPrefix = variable.componentType
? wwLib.wwElement.getComponentLabel(variable.componentType, variable.componentUid)
: null;
const label = labelPrefix ? `${labelPrefix} - ${variable.name}` : variable.name;
return {
label,
value: variable.id,
icon: variable.type,
};
});
},
},
mounted() {
this.fetchFunctions();
@ -148,6 +206,14 @@ export default {
this.isLoading = false;
}
},
setUseStreaming(useStreaming) {
this.setArgs({ useStreaming });
},
createWwVariable() {
wwLib.wwPopupSidebars.open({ name: 'NAVIGATOR' });
wwLib.$emit('wwTopBar:navigator:tab', 'data');
wwLib.$emit('wwTopBar:navigator:data:variables:set', null);
},
},
};
</script>

View File

@ -0,0 +1,372 @@
import { FunctionsHttpError, FunctionsRelayError, FunctionsFetchError } from '@supabase/supabase-js';
/**
* Builds a query string from an array or object of queries.
* @param {Array|Object} queries - Input queries.
* @returns {string} - Formatted query string (e.g., "?key=value") or empty string.
*/
export function buildQueryString(queries = []) {
const queryArray = Array.isArray(queries)
? queries
: typeof queries === 'object' && queries !== null
? Object.entries(queries).map(([key, value]) => ({ key, value }))
: [];
if (!queryArray.length) return '';
const params = new URLSearchParams();
for (const item of queryArray) {
if (item && typeof item.key === 'string') {
// Ensure key is a string
params.append(item.key, item.value ?? '');
}
}
const queryString = params.toString();
return queryString ? `?${queryString}` : '';
}
/**
* Builds a headers object from an array or object of headers.
* @param {Array|Object} headers - Input headers.
* @returns {Object<string, string>} - Headers object.
*/
export function buildHeaders(headers = []) {
const headerObject = {};
const headersArray = Array.isArray(headers)
? headers
: typeof headers === 'object' && headers !== null
? Object.entries(headers).map(([key, value]) => ({ key, value }))
: [];
for (const item of headersArray) {
if (item && typeof item.key === 'string') {
// Ensure key is a string
// Ensure value is a string or provide default for header values
headerObject[item.key] = typeof item.value === 'string' ? item.value : String(item.value ?? '');
}
}
return headerObject;
}
/**
* Parses a line from an SSE stream.
* Handles 'data: ' prefix and '[DONE]' signal.
* Attempts JSON parsing. Returns null for signals, empty lines, or non-JSON data.
* @param {string} line - The raw line content.
* @param {object} [logger=console] - Optional logger (like wwUtils or wwLib.wwLog).
* @returns {object|null} - Parsed JSON data, or null if line should be skipped.
*/
export function parseSseLine(line, logger = console) {
line = line?.trim();
if (!line) return null;
if (line.startsWith('data:')) {
line = line.substring(5).trim();
}
if (line === '[DONE]') {
logger?.debug?.('[Supabase Stream] Received [DONE] signal.');
return null;
}
if (!line) return null;
try {
return JSON.parse(line);
} catch (parseError) {
logger?.warn?.(`[Supabase Stream] Skipping non-JSON data line: "${line}"`, { parseError });
return null;
}
}
/**
* Parses the successful response body of a non-streaming function.
* @param {*} data - The raw response data.
* @returns {*} - Parsed data (object, string, or original type).
*/
export function parseFunctionResponse(data) {
try {
if (typeof data === 'object' && data !== null) return data;
if (typeof data === 'string') return JSON.parse(data);
return data;
} catch (e) {
if (typeof data === 'string') {
console.warn('[Supabase] Non-JSON string response received:', data);
return data;
}
throw new Error(`Failed to parse non-streaming function response of type ${typeof data}`, { cause: e });
}
}
/**
* Standardizes errors thrown by Supabase function invocation.
* @param {Error} error - The original error from Supabase invoke.
* @returns {Promise<Error>} - A new Error object with structured cause.
*/
export async function handleSupabaseInvokeError(error) {
const status = error?.context?.status;
let causeData = null;
let errorType = 'Unknown function invocation error';
try {
causeData = error?.context ? await error.context.text?.() : null;
if (causeData && typeof causeData === 'string') {
try {
const jsonData = JSON.parse(causeData);
causeData = jsonData;
} catch (jsonErr) {
// Keep raw text if not JSON
}
}
} catch (e) {
console.error('Failed to get Supabase error context text', e);
}
const cause = {
name: error?.name,
message: error?.message,
status,
responseBody: causeData,
originalError: error,
};
if (error instanceof FunctionsHttpError) {
errorType = `Function returned an error status: ${status || 'unknown'}`;
} else if (error instanceof FunctionsRelayError) {
errorType = `Function relay error: ${error.message}`;
} else if (error instanceof FunctionsFetchError) {
errorType = `Function fetch error: ${error.message}`;
} else if (error?.message) {
errorType = error.message;
}
const standardizedError = new Error(errorType);
standardizedError.cause = cause;
return standardizedError;
}
/**
* Executes a regular (non-streaming) edge function invocation.
* @param {Object} params - Function parameters
* @param {Object} params.instance - Supabase client instance
* @param {string} params.functionName - Name of the edge function to invoke
* @param {string} params.queryString - Query string for the request
* @param {string} params.method - HTTP method (GET, POST, etc.)
* @param {any} params.body - Request body
* @param {Object} params.headerObject - Request headers
* @param {Object} [wwUtils] - Optional logging utility
* @returns {Promise<any>} - Function response
*/
export async function executeRegularInvocation(
{ instance, functionName, queryString, method, body, headerObject },
wwUtils
) {
try {
const { data, error } = await instance.functions.invoke(functionName + queryString, {
method,
body,
headers: headerObject,
});
if (error) throw error;
const parsedData = parseFunctionResponse(data);
wwUtils?.log('info', `[Supabase] Edge function executed successfully - ${functionName}`, {
type: 'response',
preview: parsedData,
});
return parsedData;
} catch (error) {
const standardizedError = await handleSupabaseInvokeError(error);
wwUtils?.log('error', `[Supabase] Edge function error - ${functionName}`, {
type: 'error',
preview: standardizedError,
});
throw standardizedError;
}
}
/**
* Executes a streaming edge function invocation.
* @param {Object} params - Function parameters
* @param {Object} params.instance - Supabase client instance
* @param {string} params.functionName - Name of the edge function to invoke
* @param {string} params.queryString - Query string for the request
* @param {string} params.method - HTTP method (GET, POST, etc.)
* @param {any} params.body - Request body
* @param {Object} params.headerObject - Request headers
* @param {string} params.streamVariableId - ID of the variable to update with stream data
* @param {Object} params.wwLib - Window & Widgets library for variable updates
* @param {Object} [wwUtils] - Optional logging utility
* @returns {Promise<any>} - Final accumulated stream data
*/
export async function executeStreamingInvocation(
{ instance, functionName, queryString, method, body, headerObject, streamVariableId, wwLib },
wwUtils
) {
try {
wwLib.wwVariable.updateValue(streamVariableId, []);
const invokeOptions = {
method,
body: method === 'GET' ? undefined : body,
headers: {
...headerObject,
Accept: 'text/event-stream',
},
responseType: 'stream',
};
const { data: responseData, error: invokeError } = await instance.functions.invoke(
functionName + queryString,
invokeOptions
);
if (invokeError) throw invokeError;
if (!responseData || !responseData.body || typeof responseData.body.getReader !== 'function') {
console.error('[Supabase Stream Debug] Failed response data object:', responseData);
throw new Error('Response data does not contain a readable stream.');
}
const reader = responseData.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let streamActive = true;
while (streamActive) {
const { done, value } = await reader.read();
if (done) {
streamActive = false;
break;
}
const textChunk = decoder.decode(value, { stream: true });
buffer += textChunk;
let newlineIndex;
while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
let line = buffer.slice(0, newlineIndex).trim();
buffer = buffer.slice(newlineIndex + 1);
if (!line) continue; // Skip empty lines
if (line.startsWith('data:')) {
const dataContent = line.substring(5).trim();
if (dataContent === '[DONE]') {
streamActive = false;
wwUtils?.log('info', '[Supabase Stream] Received [DONE] in data: line.');
break;
}
if (dataContent) {
try {
const parsedData = JSON.parse(dataContent);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, parsedData]);
} catch (parseError) {
wwUtils?.log('warn', `[Supabase Stream] Non-JSON data in data: line: ${dataContent}`, {
parseError,
});
// Keep non-JSON data as raw string (like REST API implementation)
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, dataContent]);
}
}
} else if (line === '[DONE]') {
streamActive = false;
wwUtils?.log('info', '[Supabase Stream] Received [DONE] on its own line.');
break;
} else if (
line.startsWith('id:') ||
line.startsWith('event:') ||
line.startsWith('retry:') ||
line.startsWith(':')
) {
// SSE metadata line (but not 'data:'). Ignore it.
wwUtils?.log('debug', `[Supabase Stream] Ignoring SSE metadata line: ${line}`);
} else {
// Not 'data:', not '[DONE]', and not other known SSE metadata.
// Treat the whole line as a potential payload (e.g., newline-delimited JSON/text).
try {
const parsedData = JSON.parse(line);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, parsedData]);
} catch (parseError) {
wwUtils?.log('debug', `[Supabase Stream] Adding raw line as non-JSON: ${line}`, {
parseError,
});
// Keep non-JSON data as raw string (like REST API implementation)
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, line]);
}
}
if (!streamActive) break; // Exit outer while loop if [DONE] was encountered
}
if (!streamActive) break; // Exit outer while loop if [DONE] was encountered
}
// Process any remaining content in the buffer
const finalBufferTrimmed = buffer.trim();
if (finalBufferTrimmed) {
if (finalBufferTrimmed.startsWith('data:')) {
const dataContent = finalBufferTrimmed.substring(5).trim();
if (dataContent && dataContent !== '[DONE]') {
try {
const parsedData = JSON.parse(dataContent);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, parsedData]);
} catch (e) {
wwUtils?.log(
'warn',
`[Supabase Stream] Failed to parse JSON from final buffer data: line: ${dataContent}`,
{ e }
);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, dataContent]);
}
} else if (dataContent === '[DONE]') {
wwUtils?.log('info', '[Supabase Stream] Received [DONE] in final buffer data: line.');
}
} else if (finalBufferTrimmed === '[DONE]') {
wwUtils?.log('info', '[Supabase Stream] Received [DONE] as final buffer content.');
} else if (
finalBufferTrimmed.startsWith('id:') ||
finalBufferTrimmed.startsWith('event:') ||
finalBufferTrimmed.startsWith('retry:') ||
finalBufferTrimmed.startsWith(':')
) {
wwUtils?.log('debug', `[Supabase Stream] Ignoring SSE metadata in final buffer: ${finalBufferTrimmed}`);
} else {
// Treat the whole final buffer content as a potential payload.
try {
const parsedData = JSON.parse(finalBufferTrimmed);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, parsedData]);
} catch (e) {
wwUtils?.log(
'debug',
`[Supabase Stream] Adding raw final buffer as non-JSON: ${finalBufferTrimmed}`,
{ e }
);
const currentData = wwLib.wwVariable.getValue(streamVariableId) || [];
wwLib.wwVariable.updateValue(streamVariableId, [...currentData, finalBufferTrimmed]);
}
}
}
wwUtils?.log('info', `[Supabase] Streaming completed for Edge function - ${functionName}`, {
type: 'response',
});
return wwLib.wwVariable.getValue(streamVariableId);
} catch (error) {
const standardizedError = await handleSupabaseInvokeError(error);
wwUtils?.log('error', `[Supabase] Streaming error for Edge function - ${functionName}`, {
type: 'error',
preview: standardizedError,
});
throw standardizedError;
}
}

View File

@ -1,7 +1,14 @@
import { createClient, FunctionsHttpError, FunctionsRelayError, FunctionsFetchError } from '@supabase/supabase-js';
import { createClient } from '@supabase/supabase-js';
import { generateFilter } from './helpers/filters';
import {
buildQueryString,
buildHeaders,
executeRegularInvocation,
executeStreamingInvocation,
} from './helpers/edgeFunction.js';
export default {
instance: null,
channels: {},
@ -235,49 +242,51 @@ export default {
if (error) throw new Error(error.message, { cause: error });
return this.formatReturn(data, count);
},
async invokeEdgeFunction({ functionName, body, headers = [], queries = [], method = 'POST' }, wwUtils) {
wwUtils?.log('info', `[Supabase] Invoke an Edge function - ${functionName}`, {
async invokeEdgeFunction(args, wwUtils) {
const {
functionName,
body,
headers = [],
queries = [],
method = 'POST',
useStreaming = false,
streamVariableId = null,
} = args;
wwUtils?.log('info', `[Supabase] ${useStreaming ? 'Streaming' : 'Invoking'} Edge function - ${functionName}`, {
type: 'request',
preview: { body, headers, method },
});
const query = Array.isArray(queries)
? queries
: queries && typeof queries === 'object'
? Object.keys(queries).map(k => ({ key: k, value: queries[k] }))
: [];
const queryString = query.length
? query.reduce((result, item) => `${result}${item.key}=${item.value}&`, '?')
: '';
const { data, error } = await this.instance.functions.invoke(functionName + queryString, {
body: method === 'GET' ? undefined : body,
headers: Array.isArray(headers)
? headers.reduce((result, item) => ({ ...result, [item.key]: item.value }), {})
: headers,
method,
});
const queryString = buildQueryString(queries);
const headerObject = buildHeaders(headers);
if (error instanceof FunctionsHttpError) {
throw new Error('Function returned an error with status code ' + error.context.status, {
cause: { ...error, status: error?.context?.status, data: await error?.context?.json?.() },
});
} else if (error instanceof FunctionsRelayError) {
throw new Error('Relay error: ' + error.message, {
cause: { ...error, status: error?.context?.status, data: await error?.context?.json?.() },
});
} else if (error instanceof FunctionsFetchError) {
throw new Error('Fetch error: ' + error.message, {
cause: { ...error, status: error?.context?.status, data: await error?.context?.json?.() },
});
} else if (error) {
throw new Error(error.message, {
cause: { ...error, status: error?.context?.status, data: await error?.context?.json?.() },
});
}
try {
return JSON.parse(data);
} catch (error) {
return data;
if (useStreaming) {
return await executeStreamingInvocation(
{
instance: this.instance,
functionName,
queryString,
method,
body,
headerObject,
streamVariableId,
wwLib,
},
wwUtils
);
} else {
return await executeRegularInvocation(
{
instance: this.instance,
functionName,
queryString,
method,
body,
headerObject,
},
wwUtils
);
}
},
async listFiles({ bucket, path, options = {} }, wwUtils) {
@ -311,14 +320,14 @@ export default {
path,
options.transform
? {
transform: {
...(options.transform.format ? { format: options.transform.format } : {}),
...(options.transform.quality ? { quality: options.transform.quality } : {}),
...(options.transform.resize ? { resize: options.transform.resize } : {}),
...(options.transform.width ? { width: options.transform.width } : {}),
...(options.transform.height ? { height: options.transform.height } : {}),
},
}
transform: {
...(options.transform.format ? { format: options.transform.format } : {}),
...(options.transform.quality ? { quality: options.transform.quality } : {}),
...(options.transform.resize ? { resize: options.transform.resize } : {}),
...(options.transform.width ? { width: options.transform.width } : {}),
...(options.transform.height ? { height: options.transform.height } : {}),
},
}
: {}
);
if (error) throw new Error(error.message, { cause: error });
@ -370,12 +379,12 @@ export default {
download: options.download ? options.download.filename || true : false,
transform: options.transform
? {
...(options.transform.format ? { format: options.transform.format } : {}),
...(options.transform.quality ? { quality: options.transform.quality } : {}),
...(options.transform.resize ? { resize: options.transform.resize } : {}),
...(options.transform.width ? { width: options.transform.width } : {}),
...(options.transform.height ? { height: options.transform.height } : {}),
}
...(options.transform.format ? { format: options.transform.format } : {}),
...(options.transform.quality ? { quality: options.transform.quality } : {}),
...(options.transform.resize ? { resize: options.transform.resize } : {}),
...(options.transform.width ? { width: options.transform.width } : {}),
...(options.transform.height ? { height: options.transform.height } : {}),
}
: null,
});
} else {
@ -396,14 +405,14 @@ export default {
download: options.download ? options.download.filename || true : false,
...(options.transform
? {
transform: {
...(options.transform.format ? { format: options.transform.format } : {}),
...(options.transform.quality ? { quality: options.transform.quality } : {}),
...(options.transform.resize ? { resize: options.transform.resize } : {}),
...(options.transform.width ? { width: options.transform.width } : {}),
...(options.transform.height ? { height: options.transform.height } : {}),
},
}
transform: {
...(options.transform.format ? { format: options.transform.format } : {}),
...(options.transform.quality ? { quality: options.transform.quality } : {}),
...(options.transform.resize ? { resize: options.transform.resize } : {}),
...(options.transform.width ? { width: options.transform.width } : {}),
...(options.transform.height ? { height: options.transform.height } : {}),
},
}
: {}),
});
if (error) throw new Error(error.message, { cause: error });
@ -572,10 +581,10 @@ const applyModifiers = (query, { select, order, limit, range, single, maybeSingl
select.mode === 'minimal'
? ''
: select.mode === 'guided'
? select?.fields.length
? select.fields.join(', ')
: '*'
: select?.fieldsAdvanced
? select?.fields.length
? select.fields.join(', ')
: '*'
: select?.fieldsAdvanced
);
}

7
src/global.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
// Global type declarations for WeWeb
declare global {
var wwLib: any;
}
export {};

View File

@ -1,15 +1,24 @@
export class AsyncCache {
constructor(ttlMs = 60000) {
interface CacheItem<T> {
data: T;
timestamp: number;
}
export class AsyncCache<T = any> {
private cache: Map<string, CacheItem<T>>;
private TTL: number;
private pendingPromises: Map<string, Promise<T>>;
constructor(ttlMs: number = 60000) {
// Default 1 minute TTL
this.cache = new Map();
this.TTL = ttlMs;
this.pendingPromises = new Map();
}
async get(key, fetchFn) {
async get(key: string, fetchFn: () => Promise<T>): Promise<T> {
// Check if there's already a pending promise for this key
if (this.pendingPromises.has(key)) {
return this.pendingPromises.get(key);
return this.pendingPromises.get(key)!;
}
const cachedItem = this.cache.get(key);
@ -39,15 +48,15 @@ export class AsyncCache {
}
}
_isExpired(timestamp) {
private _isExpired(timestamp: number): boolean {
return Date.now() - timestamp > this.TTL;
}
invalidate(key) {
invalidate(key: string): void {
this.cache.delete(key);
}
clear() {
clear(): void {
this.cache.clear();
}
}

View File

@ -0,0 +1,36 @@
// eslint-disable-next-line no-unused-vars
import app from '@/_front/main.js';
// eslint-disable-next-line no-undef
import element_1b1e2173_9b78_42cc_a8ee_a6167caea340 from '@/components/elements/element-1b1e2173-9b78-42cc-a8ee-a6167caea340/src/wwElement.vue';
import element_3a7d6379_12d3_4387_98ff_b332bb492a63 from '@/components/elements/element-3a7d6379-12d3-4387-98ff-b332bb492a63/src/wwElement.vue';
import element_59dca300_db78_42e4_a7a6_0cbf22d3cc82 from '@/components/elements/element-59dca300-db78-42e4-a7a6-0cbf22d3cc82/src/wwElement.vue';
import element_69d0b3ef_b265_494c_8cd1_874da4aa1834 from '@/components/elements/element-69d0b3ef-b265-494c-8cd1-874da4aa1834/src/wwElement.vue';
import element_6f8796b1_8273_498d_95fc_7013b7c63214 from '@/components/elements/element-6f8796b1-8273-498d-95fc-7013b7c63214/src/wwElement.vue';
import element_83d890fb_84f9_4386_b459_fb4be89a8e15 from '@/components/elements/element-83d890fb-84f9-4386-b459-fb4be89a8e15/src/wwElement.vue';
import element_9256b033_f4e8_4ab4_adce_dac3a940d7f5 from '@/components/elements/element-9256b033-f4e8-4ab4-adce-dac3a940d7f5/src/wwElement.vue';
import element_aeb78b9a_6fb6_4c49_931d_faedcfad67ba from '@/components/elements/element-aeb78b9a-6fb6-4c49-931d-faedcfad67ba/src/wwElement.vue';
import element_b783dc65_d528_4f74_8c14_e27c934c39b1 from '@/components/elements/element-b783dc65-d528-4f74-8c14-e27c934c39b1/src/wwElement.vue';
import element_c6c0c00e_49fd_4cb9_bd78_5bc09945721e from '@/components/elements/element-c6c0c00e-49fd-4cb9-bd78-5bc09945721e/src/wwElement.vue';
import element_d7904e9d_fc9a_4d80_9e32_728e097879ad from '@/components/elements/element-d7904e9d-fc9a-4d80-9e32-728e097879ad/src/wwElement.vue';
import element_deb10a01_5eef_4aa1_9017_1b51c2ad6fd0 from '@/components/elements/element-deb10a01-5eef-4aa1-9017-1b51c2ad6fd0/src/wwElement.vue';
// eslint-disable-next-line no-undef
app.component('wwobject-1b1e2173-9b78-42cc-a8ee-a6167caea340', element_1b1e2173_9b78_42cc_a8ee_a6167caea340);
app.component('wwobject-3a7d6379-12d3-4387-98ff-b332bb492a63', element_3a7d6379_12d3_4387_98ff_b332bb492a63);
app.component('wwobject-59dca300-db78-42e4-a7a6-0cbf22d3cc82', element_59dca300_db78_42e4_a7a6_0cbf22d3cc82);
app.component('wwobject-69d0b3ef-b265-494c-8cd1-874da4aa1834', element_69d0b3ef_b265_494c_8cd1_874da4aa1834);
app.component('wwobject-6f8796b1-8273-498d-95fc-7013b7c63214', element_6f8796b1_8273_498d_95fc_7013b7c63214);
app.component('wwobject-83d890fb-84f9-4386-b459-fb4be89a8e15', element_83d890fb_84f9_4386_b459_fb4be89a8e15);
app.component('wwobject-9256b033-f4e8-4ab4-adce-dac3a940d7f5', element_9256b033_f4e8_4ab4_adce_dac3a940d7f5);
app.component('wwobject-aeb78b9a-6fb6-4c49-931d-faedcfad67ba', element_aeb78b9a_6fb6_4c49_931d_faedcfad67ba);
app.component('wwobject-b783dc65-d528-4f74-8c14-e27c934c39b1', element_b783dc65_d528_4f74_8c14_e27c934c39b1);
app.component('wwobject-c6c0c00e-49fd-4cb9-bd78-5bc09945721e', element_c6c0c00e_49fd_4cb9_bd78_5bc09945721e);
app.component('wwobject-d7904e9d-fc9a-4d80-9e32-728e097879ad', element_d7904e9d_fc9a_4d80_9e32_728e097879ad);
app.component('wwobject-deb10a01-5eef-4aa1-9017-1b51c2ad6fd0', element_deb10a01_5eef_4aa1_9017_1b51c2ad6fd0);
// eslint-disable-next-line no-undef
import section_99586bd3_2b15_4d6b_a025_6a50d07ca845 from '@/components/sections/section-99586bd3-2b15-4d6b-a025-6a50d07ca845/src/wwSection.vue';
// eslint-disable-next-line no-undef
app.component('section-99586bd3-2b15-4d6b-a025-6a50d07ca845', section_99586bd3_2b15_4d6b_a025_6a50d07ca845);

View File

@ -30,11 +30,11 @@ import wwobject6dcad2080a6743a2bd1d3b13ff5819cf from '@/components/elements/elem
import wwobject57831abf83ad49adba973bd30b035710 from '@/components/elements/element-57831abf-83ad-49ad-ba97-3bd30b035710/ww-config.js';
import wwobject59dca300db7842e4a7a60cbf22d3cc82 from '@/components/elements/element-59dca300-db78-42e4-a7a6-0cbf22d3cc82/ww-config.js';
import wwobject1be951afde7143e6ad1ee9b36de15529 from '@/components/elements/element-1be951af-de71-43e6-ad1e-e9b36de15529/ww-config.js';
import wwobjecta823467cbdc74ceca38c71875c4c214a from '@/components/elements/element-a823467c-bdc7-4cec-a38c-71875c4c214a/ww-config.js';
import wwobject9ae1fce82e314bfda4d20450235bdfd5 from '@/components/elements/element-9ae1fce8-2e31-4bfd-a4d2-0450235bdfd5/ww-config.js';
import wwobject9ecb2cfccef74be8b7363e17a3b7e9ff from '@/components/elements/element-9ecb2cfc-cef7-4be8-b736-3e17a3b7e9ff/ww-config.js';
import wwobjectdeb10a015eef4aa190171b51c2ad6fd0 from '@/components/elements/element-deb10a01-5eef-4aa1-9017-1b51c2ad6fd0/ww-config.js';
import wwobject985570fcb3c04566800482ab3b30a11d from '@/components/elements/element-985570fc-b3c0-4566-8004-82ab3b30a11d/ww-config.js';
import wwobjecta823467cbdc74ceca38c71875c4c214a from '@/components/elements/element-a823467c-bdc7-4cec-a38c-71875c4c214a/ww-config.js';
import wwobject9ae1fce82e314bfda4d20450235bdfd5 from '@/components/elements/element-9ae1fce8-2e31-4bfd-a4d2-0450235bdfd5/ww-config.js';
import wwobjectc6c0c00e49fd4cb9bd785bc09945721e from '@/components/elements/element-c6c0c00e-49fd-4cb9-bd78-5bc09945721e/ww-config.js';
/* wwFront:end */
@ -70,11 +70,11 @@ export const useComponentBasesStore = defineStore('componentBases', () => {
'wwobject-57831abf-83ad-49ad-ba97-3bd30b035710': getInheritedConfiguration({ ...wwobject57831abf83ad49adba973bd30b035710, name: 'wwobject-57831abf-83ad-49ad-ba97-3bd30b035710' }),
'wwobject-59dca300-db78-42e4-a7a6-0cbf22d3cc82': getInheritedConfiguration({ ...wwobject59dca300db7842e4a7a60cbf22d3cc82, name: 'wwobject-59dca300-db78-42e4-a7a6-0cbf22d3cc82' }),
'wwobject-1be951af-de71-43e6-ad1e-e9b36de15529': getInheritedConfiguration({ ...wwobject1be951afde7143e6ad1ee9b36de15529, name: 'wwobject-1be951af-de71-43e6-ad1e-e9b36de15529' }),
'wwobject-a823467c-bdc7-4cec-a38c-71875c4c214a': getInheritedConfiguration({ ...wwobjecta823467cbdc74ceca38c71875c4c214a, name: 'wwobject-a823467c-bdc7-4cec-a38c-71875c4c214a' }),
'wwobject-9ae1fce8-2e31-4bfd-a4d2-0450235bdfd5': getInheritedConfiguration({ ...wwobject9ae1fce82e314bfda4d20450235bdfd5, name: 'wwobject-9ae1fce8-2e31-4bfd-a4d2-0450235bdfd5' }),
'wwobject-9ecb2cfc-cef7-4be8-b736-3e17a3b7e9ff': getInheritedConfiguration({ ...wwobject9ecb2cfccef74be8b7363e17a3b7e9ff, name: 'wwobject-9ecb2cfc-cef7-4be8-b736-3e17a3b7e9ff' }),
'wwobject-deb10a01-5eef-4aa1-9017-1b51c2ad6fd0': getInheritedConfiguration({ ...wwobjectdeb10a015eef4aa190171b51c2ad6fd0, name: 'wwobject-deb10a01-5eef-4aa1-9017-1b51c2ad6fd0' }),
'wwobject-985570fc-b3c0-4566-8004-82ab3b30a11d': getInheritedConfiguration({ ...wwobject985570fcb3c04566800482ab3b30a11d, name: 'wwobject-985570fc-b3c0-4566-8004-82ab3b30a11d' }),
'wwobject-a823467c-bdc7-4cec-a38c-71875c4c214a': getInheritedConfiguration({ ...wwobjecta823467cbdc74ceca38c71875c4c214a, name: 'wwobject-a823467c-bdc7-4cec-a38c-71875c4c214a' }),
'wwobject-9ae1fce8-2e31-4bfd-a4d2-0450235bdfd5': getInheritedConfiguration({ ...wwobject9ae1fce82e314bfda4d20450235bdfd5, name: 'wwobject-9ae1fce8-2e31-4bfd-a4d2-0450235bdfd5' }),
'wwobject-c6c0c00e-49fd-4cb9-bd78-5bc09945721e': getInheritedConfiguration({ ...wwobjectc6c0c00e49fd4cb9bd785bc09945721e, name: 'wwobject-c6c0c00e-49fd-4cb9-bd78-5bc09945721e' })};
/* wwFront:end */

25
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_API_URL: string
readonly VITE_APP_API_WEBSOCKETS: string
readonly VITE_APP_APOLLO_URL: string
readonly VITE_APP_BUCKET_URL: string
readonly VITE_APP_CDN_URL: string
readonly VITE_APP_CDN_URL_SHORT: string
readonly VITE_APP_DASHBOARD_URL: string
readonly VITE_APP_EDITOR_DEV_URL: string
readonly VITE_APP_EDITOR_URL: string
readonly VITE_APP_ENV_MODE: string
readonly VITE_APP_HOSTNAME: string
readonly VITE_APP_IS_EDITOR_DEV: string
readonly VITE_APP_PLUGINS_URL: string
readonly VITE_APP_PREVIEW_URL: string
readonly VITE_APP_SEGMENT_KEY: string
readonly VITE_APP_STRIPE_PUBLIC_KEY: string
readonly VITE_APP_USERFLOW_KEY: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@ -1,6 +1,7 @@
import { ref, inject, computed, unref, watch } from 'vue';
import { getLayoutStyleFromContent } from '@/_front/helpers/wwLayoutStyle';
import { useRegisterElementLocalContext } from '@/_front/use/useElementLocalContext';
import { getComponentLabel } from '@/_common/helpers/component/component.js';
/* wwFront:start */
import { useRoute } from 'vue-router';
/* wwFront:end */
@ -399,4 +400,9 @@ export default {
* @PUBLIC_API
*/
useRegisterElementLocalContext,
/**
* @PUBLIC_API
*/
getComponentLabel,
};

View File

@ -7,7 +7,7 @@ export default {
* @DEPRECATED wwLib.wwUtils.getUid
*/
getUniqueId() {
wwLib.wwLog.warn('wwLib.wwUtils.getUniqueId is deprecated, use wwLib.wwLib.getUid instead');
wwLib.wwLog.warn('wwLib.wwUtils.getUniqueId is deprecated, use wwLib.wwUtils.getUid instead');
var d = new Date();
return Math.floor((d.getTime() * Math.random() + Math.random() * 10000 + Math.random() * 100) / 100);
},

View File

@ -12,20 +12,20 @@
<link rel="icon" type="image/x-icon" href="favicon.ico?_wwcv={{cacheVersion}}" />
<link rel="manifest" href="manifest.json?_wwcv=182" />
<link rel="manifest" href="manifest.json?_wwcv=183" />
<meta name="theme-color" content="" />
<link rel="apple-touch-icon" sizes="48x48" href="images/48-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="72x72" href="images/72-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="96x96" href="images/96-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="128x128" href="images/128-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="144x144" href="images/144-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="152x152" href="images/152-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="192x192" href="images/192-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="256x256" href="images/256-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="384x384" href="images/384-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="512x512" href="images/512-favicon.png?_wwcv=182">
<link rel="apple-touch-icon" sizes="48x48" href="images/48-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="72x72" href="images/72-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="96x96" href="images/96-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="128x128" href="images/128-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="144x144" href="images/144-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="152x152" href="images/152-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="192x192" href="images/192-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="256x256" href="images/256-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="384x384" href="images/384-favicon.png?_wwcv=183">
<link rel="apple-touch-icon" sizes="512x512" href="images/512-favicon.png?_wwcv=183">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link href="/fonts/Phosphor/font.css?_wwcv=182" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link href="/fonts/Phosphor/font.css?_wwcv=183" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<style>:root{ --ww-default-font-family: 'Raleway', sans-serif }</style>
<style>

25
tsconfig.app.json Normal file
View File

@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vue/tsconfig/tsconfig.dom.json",
// Based on https://github.com/vitejs/vite/blob/main/packages/create-vite/template-vue-ts/tsconfig.json
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
},
"allowJs": true,
"checkJs": false,
"resolveJsonModule": true,
"noEmit": true,
/* Linting */
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.js", "src/**/*.jsx"]
}

27
tsconfig.node.json Normal file
View File

@ -0,0 +1,27 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
// Based on https://github.com/vitejs/vite/blob/main/packages/create-vite/template-vue-ts/tsconfig.node.json
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"allowJs": true,
"checkJs": false,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.js"]
}

File diff suppressed because one or more lines are too long