v189 - lessons

This commit is contained in:
Arseny Sazhin (via WeWeb) 2025-10-07 12:16:43 +03:00
parent 2de028e570
commit 8c20931f59
48 changed files with 1598 additions and 113 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","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"]}
{"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","@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","lodash-es":"^4.17.21"},"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":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":[]}
{"cacheVersion":189,"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

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

View File

@ -1,4 +1,4 @@
const version = 183;
const version = 189;
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

@ -200,15 +200,15 @@ function mapFilterSortData(rawValue, filter, sort, __wwmap, context, event, args
let value = rawValue;
if (Array.isArray(value)) {
let data = value; // TODO: remove this when no side effects are expected
if (__wwmap) data = mapData(value, __wwmap, context, event, args);
if (filter) data = filterData(data, filter, context, event, args);
if (sort) data = sortData([...data], sort, context, event, args);
if (__wwmap) data = mapData(data, __wwmap, context, event, args);
return { value: data, rawValue };
} else if (isObject(value) && Array.isArray(value.data) && value.type === 'collection') {
let data = value.data; // TODO: remove this when no side effects are expected
if (__wwmap) data = mapData(value.data, __wwmap, context, event, args);
if (filter) data = filterData(data, filter, context, event, args);
if (sort) data = sortData([...data], sort, context, event, args);
if (__wwmap) data = mapData(data, __wwmap, context, event, args);
return { value: { ...value, data, total: data.length }, rawValue };
}
return { value, rawValue };

File diff suppressed because one or more lines are too long

View File

@ -52,6 +52,7 @@ export function useForm(
validation,
customValidation = shallowRef(false),
required = shallowRef(false),
requiredValidation = null,
initialValue = undefined,
},
{ elementState, emit, sidepanelFormPath = 'form', setValue = null }
@ -60,6 +61,7 @@ export function useForm(
const registerFormInput = inject('_wwForm:registerInput', () => {});
const unregisterFormInput = inject('_wwForm:unregisterInput', () => {});
const updateFormInput = inject('_wwForm:updateInput', () => {});
const submitForm = inject('_wwForm:submit', () => {});
const id = wwLib.wwUtils.getUid();
const _fieldName = computed(() => fieldName?.value || elementState.name);
@ -95,9 +97,11 @@ export function useForm(
}
const { resolveFormula } = wwLib.wwFormula.useFormula();
const computeValidation = (value, required, customValidation, validation) => {
const computeValidation = (value, required, customValidation, validation, requiredValidation) => {
const validationResult = customValidation && validation ? resolveFormula(validation)?.value : true;
const hasValue = !isValueEmpty(value);
// Use custom required validation if provided, otherwise use default isEmpty check
const hasValue = requiredValidation ? requiredValidation(value) : !isValueEmpty(value);
// If not required, field is valid unless there's custom validation
if (!required) {
@ -109,7 +113,7 @@ export function useForm(
return hasValue && validationResult;
}
// If just required, check for value
// If just required, check for value using custom or default validation
return hasValue;
};
@ -135,7 +139,7 @@ export function useForm(
let isFirst = true;
const computedValidation = computed(() => {
// We have to compute the validation here, otherwise the reactivity will not work
const isValid = computeValidation(value.value, required.value, customValidation.value, validation.value);
const isValid = computeValidation(value.value, required.value, customValidation.value, validation.value, requiredValidation);
if (isFirst) {
isFirst = false;
return null;
@ -159,7 +163,7 @@ export function useForm(
validationType => {
if (validationType === 'change') {
updateInputValidity(
computeValidation(value.value, required?.value, customValidation?.value, validation?.value)
computeValidation(value.value, required?.value, customValidation?.value, validation?.value, requiredValidation)
);
} else if (validationType === 'submit') {
updateInputValidity(true);
@ -171,7 +175,7 @@ export function useForm(
});
function forceValidateField() {
debouncedUpdateInputValidity.cancel();
const isValid = computeValidation(value.value, required?.value, customValidation?.value, validation?.value);
const isValid = computeValidation(value.value, required?.value, customValidation?.value, validation?.value, requiredValidation);
updateInputValidity(isValid);
return isValid;
}
@ -204,6 +208,7 @@ export function useForm(
return {
selectForm,
submitForm,
};
}

View File

@ -177,6 +177,7 @@ export default {
validationType,
debounceDelay,
});
provide('_wwForm:submit', handleSubmit);
provide('_wwForm:useForm', useForm);
const markdown = `### Form local informations

View File

@ -0,0 +1,26 @@
---
name: cookie-reader
description: A component that reads and displays cookie values, including the vkid_sdk:codeVerifier cookie.
keywords: cookies, vkid, code verifier, authentication
---
#### Cookie Reader Component
***Purpose:***
This component reads and displays cookie values from the browser, with special focus on the vkid_sdk:codeVerifier cookie which is often used in authentication flows.
***Features:***
- Reads all cookies from the browser
- Specifically extracts and displays the vkid_sdk:codeVerifier cookie value
- Updates automatically when cookies change
***Properties:***
- No configurable properties are required as the component directly reads from browser cookies
***Exposed Variables:***
- codeVerifier: The current value of the vkid_sdk:codeVerifier cookie (path: variables['current_element_uid-codeVerifier'])
***Notes:***
- This component is useful for debugging authentication flows that use the PKCE (Proof Key for Code Exchange) method
- The codeVerifier is typically used in OAuth 2.0 authorization code flow with PKCE
- The component will show nothing if the vkid_sdk:codeVerifier cookie is not present

View File

@ -0,0 +1 @@
{"name":"cookie-reader","version":"1.0.1","scripts":{"build":"weweb build","serve":"weweb serve"},"devDependencies":{}}

View File

@ -0,0 +1,883 @@
<template>
<div class="vk-auth-component" :class="{ 'is-loading': isLoading }">
<!-- OAuth 2.0 Redirect Button -->
<button
v-if="content.authMethod === 'oauth'"
class="vk-auth-button"
:style="buttonStyle"
@click="initiateOAuthLogin"
:disabled="isLoading"
>
<div class="vk-auth-button__content">
<div class="vk-auth-button__icon" v-if="!content.hideIcon">
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
d="M15.684 0H8.316C1.592 0 0 1.592 0 8.316v7.368C0 22.408 1.592 24 8.316 24h7.368C22.408 24 24 22.408 24 15.684V8.316C24 1.592 22.408 0 15.684 0zm3.692 17.123h-1.744c-.66 0-.862-.523-2.049-1.72-1.033-1.01-1.49-1.135-1.744-1.135-.356 0-.458.102-.458.593v1.573c0 .424-.136.593-1.252.593-1.844 0-3.896-1.122-5.342-3.208-2.17-3.05-2.778-5.342-2.778-5.817 0-.254.102-.491.593-.491h1.744c.44 0 .61.203.78.678.864 2.468 2.303 4.63 2.896 4.63.22 0 .322-.102.322-.66V9.473c-.068-1.186-.695-1.287-.695-1.71 0-.203.17-.407.44-.407h2.743c.373 0 .508.203.508.643v3.463c0 .373.17.508.271.508.22 0 .407-.135.813-.542 1.27-1.422 2.183-3.615 2.183-3.615.119-.254.305-.491.729-.491h1.744c.525 0 .644.271.525.643-.22 1.032-2.387 4.07-2.387 4.07-.186.305-.254.44 0 .78.186.254.796.779 1.2 1.252.745.864 1.32 1.592 1.473 2.1.17.508-.085.762-.576.762z"
/>
</svg>
</div>
<span class="vk-auth-button__text">{{ content.buttonText }}</span>
</div>
</button>
<!-- VK ID One Tap Container -->
<div v-else-if="content.authMethod === 'onetap'" ref="vkIdContainer" class="vk-id-container"></div>
<!-- Loading Indicator -->
<div v-if="isLoading" class="vk-auth-loading">
<div class="vk-auth-loading__spinner"></div>
</div>
<!-- Telegram WebApp Info (hidden) -->
<div v-if="content.showTelegramInfo" class="telegram-info">
<div class="telegram-info__item">
<span class="telegram-info__label">Viewport Height:</span>
<span class="telegram-info__value">{{ telegramViewportHeight }}</span>
</div>
<div class="telegram-info__item">
<span class="telegram-info__label">Safe Area Inset:</span>
<span class="telegram-info__value">{{ telegramSafeAreaInset }}</span>
</div>
<div class="telegram-info__item">
<span class="telegram-info__label">Platform:</span>
<span class="telegram-info__value">{{ telegramPlatform }}</span>
</div>
<div class="telegram-info__item">
<span class="telegram-info__label">Color Scheme:</span>
<span class="telegram-info__value">{{ telegramColorScheme }}</span>
</div>
</div>
</div>
</template>
<script>
import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
export default {
props: {
content: { type: Object, required: true },
uid: { type: String, required: true },
},
emits: ['trigger-event'],
setup(props, { emit }) {
// Editor state
const isEditing = computed(() => {
// eslint-disable-next-line no-unreachable
return false;
});
// Component state
const isLoading = ref(false);
const vkIdContainer = ref(null);
const vkSdkLoaded = ref(false);
const oneTapInstance = ref(null);
// Internal variables to expose to WeWeb
const { value: authCode, setValue: setAuthCode } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'authCode',
type: 'string',
defaultValue: '',
});
const { value: deviceId, setValue: setDeviceId } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'deviceId',
type: 'string',
defaultValue: '',
});
const { value: authError, setValue: setAuthError } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'authError',
type: 'string',
defaultValue: '',
});
// Telegram WebApp internal variables
const { value: telegramViewportHeight, setValue: setTelegramViewportHeight } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramViewportHeight',
type: 'number',
defaultValue: 0,
});
const { value: telegramSafeAreaInset, setValue: setTelegramSafeAreaInset } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramSafeAreaInset',
type: 'object',
defaultValue: { top: 0, right: 0, bottom: 0, left: 0 },
});
const { value: telegramPlatform, setValue: setTelegramPlatform } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramPlatform',
type: 'string',
defaultValue: '',
});
const { value: telegramColorScheme, setValue: setTelegramColorScheme } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramColorScheme',
type: 'string',
defaultValue: '',
});
const { value: telegramThemeParams, setValue: setTelegramThemeParams } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramThemeParams',
type: 'object',
defaultValue: {},
});
const { value: telegramInitData, setValue: setTelegramInitData } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramInitData',
type: 'string',
defaultValue: '',
});
const { value: telegramInitDataUnsafe, setValue: setTelegramInitDataUnsafe } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramInitDataUnsafe',
type: 'object',
defaultValue: {},
});
const { value: telegramHeaderColor, setValue: setTelegramHeaderColor } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramHeaderColor',
type: 'string',
defaultValue: '',
});
const { value: telegramBackgroundColor, setValue: setTelegramBackgroundColor } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramBackgroundColor',
type: 'string',
defaultValue: '',
});
const { value: telegramIsExpanded, setValue: setTelegramIsExpanded } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramIsExpanded',
type: 'boolean',
defaultValue: false,
});
const { value: telegramVersion, setValue: setTelegramVersion } = wwLib.wwVariable.useComponentVariable({
uid: props.uid,
name: 'telegramVersion',
type: 'string',
defaultValue: '',
});
// Computed properties
const buttonStyle = computed(() => {
return {
backgroundColor: props.content?.buttonColor || '#0077FF',
color: props.content?.buttonTextColor || '#FFFFFF',
borderRadius: props.content?.buttonBorderRadius || '4px',
padding: props.content?.buttonPadding || '10px 16px',
fontSize: props.content?.buttonFontSize || '14px',
fontWeight: props.content?.buttonFontWeight || '500',
border: props.content?.buttonBorder || 'none',
};
});
// Methods
const validateAppConfig = () => {
if (!props.content?.appId) {
setAuthError('VK App ID is required');
emitError('VK App ID is required');
return false;
}
if (!props.content?.redirectUri) {
setAuthError('Redirect URI is required');
emitError('Redirect URI is required');
return false;
}
return true;
};
const loadVkSdk = () => {
if (isEditing.value || vkSdkLoaded.value) return;
const document = wwLib.getFrontDocument();
const script = document.createElement('script');
script.src = 'https://unpkg.com/@vkid/sdk@2.6.0/dist-sdk/umd/index.js';
script.async = true;
script.onload = () => {
vkSdkLoaded.value = true;
if (props.content?.authMethod === 'onetap') {
initVkIdOneTap();
}
};
script.onerror = () => {
setAuthError('Failed to load VK SDK');
emitError('Failed to load VK SDK');
};
document.head.appendChild(script);
};
const initVkIdOneTap = () => {
if (isEditing.value || !vkSdkLoaded.value || !vkIdContainer.value) return;
if (!validateAppConfig()) {
return;
}
const window = wwLib.getFrontWindow();
if (!window.VKIDSDK) {
setAuthError('VK SDK not loaded properly');
emitError('VK SDK not loaded properly');
return;
}
try {
const VKID = window.VKIDSDK;
// Initialize VKID config
VKID.Config.init({
app: parseInt(props.content?.appId, 10),
redirectUrl: props.content?.redirectUri,
responseMode: props.content?.backendTokenExchange === true
? VKID.ConfigResponseMode.Code
: VKID.ConfigResponseMode.Callback,
source: VKID.ConfigSource.LOWCODE,
scope: props.content?.scopes || '',
});
// Create and render OneTap
oneTapInstance.value = new VKID.OneTap({
showAgreements: props.content?.showAgreements !== false,
skin: props.content?.oneTapButtonSkin || 'primary',
buttonSkin: props.content?.oneTapButtonSkin || 'primary',
buttonSize: props.content?.oneTapButtonSize || 'large',
});
oneTapInstance.value.render({
container: vkIdContainer.value,
showAlternativeLogin: props.content?.showAlternativeLogin !== false
})
.on(VKID.WidgetEvents.ERROR, handleOneTapError)
.on(VKID.OneTapInternalEvents.LOGIN_SUCCESS, (payload) => {
const code = payload.code;
const deviceId = payload.device_id;
// Handle success directly or exchange code if needed
handleOneTapSuccess({
code: code,
device_id: deviceId
});
});
} catch (error) {
setAuthError(`VK SDK initialization error: ${error.message}`);
emitError(`VK SDK initialization error: ${error.message}`);
}
};
const handleOneTapSuccess = (data) => {
if (!data) {
setAuthError('Invalid response from VK ID');
emitError('Invalid response from VK ID');
return;
}
const code = data.code;
const device_id = data.device_id;
let code_verifier = '';
// Extract code_verifier from cookies if backendTokenExchange is enabled
if (props.content?.backendTokenExchange === true) {
const document = wwLib.getFrontDocument();
code_verifier = (document.cookie.match(/(?:^|; )VK_code_verifier=([^;]+)/)||[])[1]||'';
}
// Set internal variables
setAuthCode(code || '');
setDeviceId(device_id || '');
setAuthError('');
// Emit success event with code_verifier
emit('trigger-event', {
name: 'success',
event: {
code: code || '',
device_id: device_id || '',
code_verifier: code_verifier
}
});
};
const initiateOAuthLogin = () => {
if (isEditing.value) return;
if (!validateAppConfig()) {
return;
}
try {
isLoading.value = true;
const window = wwLib.getFrontWindow();
// Build OAuth URL
const oauthUrl = new URL('https://oauth.vk.com/authorize');
oauthUrl.searchParams.append('client_id', props.content?.appId);
oauthUrl.searchParams.append('redirect_uri', props.content?.redirectUri);
oauthUrl.searchParams.append('scope', props.content?.scopes || 'email');
oauthUrl.searchParams.append('response_type', 'code');
oauthUrl.searchParams.append('v', '5.131'); // Ensure API version is set
// Add optional parameters if provided
if (props.content?.state) {
oauthUrl.searchParams.append('state', props.content?.state);
}
// Redirect to VK OAuth page
window.location.href = oauthUrl.toString();
} catch (error) {
isLoading.value = false;
setAuthError(`OAuth redirect error: ${error.message}`);
emitError(`OAuth redirect error: ${error.message}`);
}
};
const handleOneTapError = (error) => {
const errorMessage = error?.error_description || error?.error || 'Unknown VK ID error';
setAuthError(errorMessage);
emitError(errorMessage);
};
const checkForOAuthCode = () => {
if (isEditing.value) return;
try {
const window = wwLib.getFrontWindow();
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const error = urlParams.get('error');
const errorDescription = urlParams.get('error_description');
if (code) {
// Set internal variables
setAuthCode(code);
setDeviceId('');
setAuthError('');
// Emit success event
emit('trigger-event', {
name: 'success',
event: {
code: code,
device_id: '',
}
});
// Clean URL if configured to do so
if (props.content?.cleanUrlAfterAuth) {
const newUrl = window.location.href.split('?')[0];
window.history.replaceState({}, document.title, newUrl);
}
} else if (error || errorDescription) {
// Handle specific VK error messages
let errorMsg = errorDescription || error || 'Unknown OAuth error';
// Check for common VK error patterns
if (errorMsg.includes('способ авторизации не доступен')) {
errorMsg = 'The selected authentication method is not available for this application. Please check your VK app settings and ensure it has the required permissions.';
}
setAuthError(errorMsg);
emitError(errorMsg);
}
} catch (error) {
setAuthError(`Error checking OAuth response: ${error.message}`);
emitError(`Error checking OAuth response: ${error.message}`);
}
};
const emitError = (errorMessage) => {
emit('trigger-event', {
name: 'error',
event: {
error: errorMessage
}
});
};
// Telegram WebApp methods
const initTelegramWebApp = () => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) {
// Not running in Telegram WebApp
return;
}
try {
const tg = window.Telegram.WebApp;
// Set all Telegram WebApp variables
setTelegramViewportHeight(tg.viewportHeight || 0);
setTelegramSafeAreaInset(tg.safeAreaInset || { top: 0, right: 0, bottom: 0, left: 0 });
setTelegramPlatform(tg.platform || '');
setTelegramColorScheme(tg.colorScheme || '');
setTelegramThemeParams(tg.themeParams || {});
setTelegramInitData(tg.initData || '');
setTelegramInitDataUnsafe(tg.initDataUnsafe || {});
setTelegramHeaderColor(tg.headerColor || '');
setTelegramBackgroundColor(tg.backgroundColor || '');
setTelegramIsExpanded(tg.isExpanded || false);
setTelegramVersion(tg.version || '');
// Set up event listeners for Telegram WebApp events
tg.onEvent('viewportChanged', updateTelegramViewportData);
tg.onEvent('themeChanged', updateTelegramThemeData);
tg.onEvent('mainButtonClicked', () => {
emit('trigger-event', {
name: 'telegramMainButtonClicked',
event: {}
});
});
tg.onEvent('backButtonClicked', () => {
emit('trigger-event', {
name: 'telegramBackButtonClicked',
event: {}
});
});
tg.onEvent('settingsButtonClicked', () => {
emit('trigger-event', {
name: 'telegramSettingsButtonClicked',
event: {}
});
});
tg.onEvent('invoiceClosed', (eventData) => {
emit('trigger-event', {
name: 'telegramInvoiceClosed',
event: eventData || {}
});
});
tg.onEvent('popupClosed', (eventData) => {
emit('trigger-event', {
name: 'telegramPopupClosed',
event: eventData || {}
});
});
tg.onEvent('qrTextReceived', (eventData) => {
emit('trigger-event', {
name: 'telegramQrTextReceived',
event: { text: eventData || '' }
});
});
tg.onEvent('clipboardTextReceived', (eventData) => {
emit('trigger-event', {
name: 'telegramClipboardTextReceived',
event: { text: eventData || '' }
});
});
// Emit telegramWebAppReady event
emit('trigger-event', {
name: 'telegramWebAppReady',
event: {
platform: tg.platform,
version: tg.version
}
});
} catch (error) {
console.error('Error initializing Telegram WebApp:', error);
}
};
const updateTelegramViewportData = () => {
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
const tg = window.Telegram.WebApp;
setTelegramViewportHeight(tg.viewportHeight || 0);
setTelegramSafeAreaInset(tg.safeAreaInset || { top: 0, right: 0, bottom: 0, left: 0 });
setTelegramIsExpanded(tg.isExpanded || false);
emit('trigger-event', {
name: 'telegramViewportChanged',
event: {
viewportHeight: tg.viewportHeight,
isExpanded: tg.isExpanded,
safeAreaInset: tg.safeAreaInset
}
});
};
const updateTelegramThemeData = () => {
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
const tg = window.Telegram.WebApp;
setTelegramColorScheme(tg.colorScheme || '');
setTelegramThemeParams(tg.themeParams || {});
setTelegramHeaderColor(tg.headerColor || '');
setTelegramBackgroundColor(tg.backgroundColor || '');
emit('trigger-event', {
name: 'telegramThemeChanged',
event: {
colorScheme: tg.colorScheme,
themeParams: tg.themeParams
}
});
};
// Telegram WebApp action methods
const telegramExpandApp = () => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
window.Telegram.WebApp.expand();
} catch (error) {
console.error('Error expanding Telegram WebApp:', error);
}
};
const telegramCloseApp = () => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
window.Telegram.WebApp.close();
} catch (error) {
console.error('Error closing Telegram WebApp:', error);
}
};
const telegramSetupMainButton = (params) => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
const mainButton = window.Telegram.WebApp.MainButton;
if (params?.text) mainButton.setText(params.text);
if (params?.color) mainButton.setBackgroundColor(params.color);
if (params?.textColor) mainButton.setTextColor(params.textColor);
if (params?.isVisible !== undefined) {
params.isVisible ? mainButton.show() : mainButton.hide();
}
if (params?.isActive !== undefined) {
params.isActive ? mainButton.enable() : mainButton.disable();
}
if (params?.isProgressVisible !== undefined) {
params.isProgressVisible ? mainButton.showProgress() : mainButton.hideProgress();
}
} catch (error) {
console.error('Error setting up Telegram MainButton:', error);
}
};
const telegramSetupBackButton = (isVisible) => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
const backButton = window.Telegram.WebApp.BackButton;
isVisible ? backButton.show() : backButton.hide();
} catch (error) {
console.error('Error setting up Telegram BackButton:', error);
}
};
const telegramShowPopup = (params) => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
window.Telegram.WebApp.showPopup(params, (buttonId) => {
emit('trigger-event', {
name: 'telegramPopupClosed',
event: { buttonId }
});
});
} catch (error) {
console.error('Error showing Telegram popup:', error);
}
};
const telegramShowAlert = (message) => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
window.Telegram.WebApp.showAlert(message, () => {
emit('trigger-event', {
name: 'telegramAlertClosed',
event: {}
});
});
} catch (error) {
console.error('Error showing Telegram alert:', error);
}
};
const telegramShowConfirm = (message) => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
window.Telegram.WebApp.showConfirm(message, (confirmed) => {
emit('trigger-event', {
name: 'telegramConfirmClosed',
event: { confirmed }
});
});
} catch (error) {
console.error('Error showing Telegram confirm:', error);
}
};
const telegramScanQrCode = () => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
window.Telegram.WebApp.showScanQrPopup({}, (text) => {
emit('trigger-event', {
name: 'telegramQrTextReceived',
event: { text }
});
});
} catch (error) {
console.error('Error showing Telegram QR scanner:', error);
}
};
const telegramReadClipboard = () => {
if (isEditing.value) return;
const window = wwLib.getFrontWindow();
if (!window.Telegram || !window.Telegram.WebApp) return;
try {
window.Telegram.WebApp.readTextFromClipboard((text) => {
emit('trigger-event', {
name: 'telegramClipboardTextReceived',
event: { text }
});
});
} catch (error) {
console.error('Error reading from Telegram clipboard:', error);
}
};
// Lifecycle hooks
onMounted(() => {
if (!isEditing.value) {
// Check for OAuth code in URL (for redirect flow)
checkForOAuthCode();
// Load VK SDK (for One Tap flow)
if (props.content?.authMethod === 'onetap') {
loadVkSdk();
}
// Initialize Telegram WebApp if available
initTelegramWebApp();
}
});
onUnmounted(() => {
// Cleanup if needed
oneTapInstance.value = null;
// Remove Telegram WebApp event listeners if needed
const window = wwLib.getFrontWindow();
if (window.Telegram && window.Telegram.WebApp) {
try {
window.Telegram.WebApp.offEvent('viewportChanged', updateTelegramViewportData);
window.Telegram.WebApp.offEvent('themeChanged', updateTelegramThemeData);
} catch (error) {
console.error('Error removing Telegram WebApp event listeners:', error);
}
}
});
// Watch for changes in configuration
watch(() => props.content?.authMethod, (newMethod) => {
if (newMethod === 'onetap' && !isEditing.value) {
if (vkSdkLoaded.value) {
initVkIdOneTap();
} else {
loadVkSdk();
}
}
});
watch(() => props.content?.appId, () => {
if (props.content?.authMethod === 'onetap' && vkSdkLoaded.value && !isEditing.value) {
initVkIdOneTap();
}
});
return {
isEditing,
isLoading,
vkIdContainer,
buttonStyle,
initiateOAuthLogin,
authCode,
deviceId,
authError,
// Telegram WebApp variables
telegramViewportHeight,
telegramSafeAreaInset,
telegramPlatform,
telegramColorScheme,
telegramThemeParams,
telegramInitData,
telegramInitDataUnsafe,
telegramHeaderColor,
telegramBackgroundColor,
telegramIsExpanded,
telegramVersion,
// Telegram WebApp methods
telegramExpandApp,
telegramCloseApp,
telegramSetupMainButton,
telegramSetupBackButton,
telegramShowPopup,
telegramShowAlert,
telegramShowConfirm,
telegramScanQrCode,
telegramReadClipboard,
};
}
};
</script>
<style lang="scss" scoped>
.vk-auth-component {
position: relative;
display: inline-block;
&.is-loading {
opacity: 0.7;
pointer-events: none;
}
}
.vk-auth-button {
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
transition: opacity 0.2s ease;
width: 100%;
&:hover {
opacity: 0.9;
}
&:active {
opacity: 0.8;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
&__content {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
&__icon {
display: flex;
align-items: center;
justify-content: center;
}
&__text {
white-space: nowrap;
}
}
.vk-id-container {
min-height: 40px;
width: 100%;
}
.vk-auth-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.5);
&__spinner {
width: 20px;
height: 20px;
border: 2px solid rgba(0, 119, 255, 0.3);
border-radius: 50%;
border-top-color: #0077FF;
animation: spin 1s linear infinite;
}
}
.telegram-info {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
&__item {
display: flex;
margin-bottom: 5px;
}
&__label {
font-weight: 500;
margin-right: 5px;
}
&__value {
color: #666;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,551 @@
export default {
editor: {
label: {
en: 'VK Auth & Telegram WebApp',
ru: 'Авторизация ВК и Telegram WebApp'
},
icon: 'login',
},
properties: {
appId: {
label: {
en: 'VK App ID',
ru: 'ID приложения ВК'
},
type: 'Text',
section: 'settings',
bindable: true,
defaultValue: '',
},
appSetupInfo: {
label: {
en: 'VK App Setup Info',
ru: 'Информация о настройке приложения ВК'
},
type: 'Info',
section: 'settings',
options: {
text: 'Important: Your VK application must be properly configured in the VK Developer Dashboard. For OAuth flow, ensure your app has "Open API" enabled and the redirect URI is whitelisted. For VK ID One Tap, your app must have "VK ID" integration enabled with the latest SDK version.'
},
},
redirectUri: {
label: {
en: 'Redirect URI',
ru: 'URI перенаправления'
},
type: 'Text',
section: 'settings',
bindable: true,
defaultValue: '',
},
scopes: {
label: {
en: 'Scopes',
ru: 'Разрешения'
},
type: 'Text',
section: 'settings',
bindable: true,
defaultValue: 'email',
},
authMethod: {
label: {
en: 'Auth Method',
ru: 'Метод авторизации'
},
type: 'TextSelect',
section: 'settings',
bindable: true,
defaultValue: 'oauth',
options: {
options: [
{ value: 'oauth', label: 'OAuth 2.0 (Redirect)' },
{ value: 'onetap', label: 'VK ID One Tap' },
]
},
},
state: {
label: {
en: 'State Parameter',
ru: 'Параметр state'
},
type: 'Text',
section: 'settings',
bindable: true,
defaultValue: '',
},
cleanUrlAfterAuth: {
label: {
en: 'Clean URL After Auth',
ru: 'Очистить URL после авторизации'
},
type: 'OnOff',
section: 'settings',
bindable: true,
defaultValue: true,
},
buttonText: {
label: {
en: 'Button Text',
ru: 'Текст кнопки'
},
type: 'Text',
section: 'style',
bindable: true,
defaultValue: 'Sign in with VK',
hidden: content => content.authMethod === 'onetap',
},
hideIcon: {
label: {
en: 'Hide VK Icon',
ru: 'Скрыть иконку ВК'
},
type: 'OnOff',
section: 'style',
bindable: true,
defaultValue: false,
hidden: content => content.authMethod === 'onetap',
},
buttonColor: {
label: {
en: 'Button Color',
ru: 'Цвет кнопки'
},
type: 'Color',
section: 'style',
bindable: true,
defaultValue: '#0077FF',
hidden: content => content.authMethod === 'onetap',
},
buttonTextColor: {
label: {
en: 'Button Text Color',
ru: 'Цвет текста кнопки'
},
type: 'Color',
section: 'style',
bindable: true,
defaultValue: '#FFFFFF',
hidden: content => content.authMethod === 'onetap',
},
buttonBorderRadius: {
label: {
en: 'Button Border Radius',
ru: 'Радиус скругления кнопки'
},
type: 'Length',
section: 'style',
bindable: true,
defaultValue: '4px',
hidden: content => content.authMethod === 'onetap',
},
buttonPadding: {
label: {
en: 'Button Padding',
ru: 'Внутренний отступ кнопки'
},
type: 'Text',
section: 'style',
bindable: true,
defaultValue: '10px 16px',
hidden: content => content.authMethod === 'onetap',
},
buttonFontSize: {
label: {
en: 'Button Font Size',
ru: 'Размер шрифта кнопки'
},
type: 'Length',
section: 'style',
bindable: true,
defaultValue: '14px',
hidden: content => content.authMethod === 'onetap',
},
buttonFontWeight: {
label: {
en: 'Button Font Weight',
ru: 'Толщина шрифта кнопки'
},
type: 'TextSelect',
section: 'style',
bindable: true,
defaultValue: '500',
options: {
options: [
{ value: '300', label: 'Light' },
{ value: '400', label: 'Regular' },
{ value: '500', label: 'Medium' },
{ value: '600', label: 'Semi Bold' },
{ value: '700', label: 'Bold' },
]
},
hidden: content => content.authMethod === 'onetap',
},
buttonBorder: {
label: {
en: 'Button Border',
ru: 'Граница кнопки'
},
type: 'Text',
section: 'style',
bindable: true,
defaultValue: 'none',
hidden: content => content.authMethod === 'onetap',
},
oneTapButtonSkin: {
label: {
en: 'One Tap Button Skin',
ru: 'Стиль кнопки One Tap'
},
type: 'TextSelect',
section: 'style',
bindable: true,
defaultValue: 'primary',
options: {
options: [
{ value: 'primary', label: 'Primary' },
{ value: 'flat', label: 'Flat' },
]
},
hidden: content => content.authMethod !== 'onetap',
},
oneTapButtonSize: {
label: {
en: 'One Tap Button Size',
ru: 'Размер кнопки One Tap'
},
type: 'TextSelect',
section: 'style',
bindable: true,
defaultValue: 'large',
options: {
options: [
{ value: 'large', label: 'Large' },
{ value: 'medium', label: 'Medium' },
{ value: 'small', label: 'Small' },
]
},
hidden: content => content.authMethod !== 'onetap',
},
showAgreements: {
label: {
en: 'Show Agreements',
ru: 'Показывать соглашения'
},
type: 'OnOff',
section: 'settings',
bindable: true,
defaultValue: true,
hidden: content => content.authMethod !== 'onetap',
},
showAlternativeLogin: {
label: {
en: 'Show Alternative Login',
ru: 'Показывать альтернативный вход'
},
type: 'OnOff',
section: 'settings',
bindable: true,
defaultValue: true,
hidden: content => content.authMethod !== 'onetap',
},
backendTokenExchange: {
label: {
en: 'Backend token exchange',
ru: 'Обмен токенов на бэкенде'
},
type: 'OnOff',
section: 'settings',
bindable: true,
defaultValue: false,
hidden: content => content.authMethod !== 'onetap',
},
// Telegram WebApp settings
telegramSection: {
label: {
en: 'Telegram WebApp',
ru: 'Telegram WebApp'
},
type: 'Section',
section: 'settings',
},
showTelegramInfo: {
label: {
en: 'Show Telegram Info',
ru: 'Показать информацию Telegram'
},
type: 'OnOff',
section: 'settings',
bindable: true,
defaultValue: false,
},
telegramInfoText: {
label: {
en: 'Telegram WebApp Info',
ru: 'Информация о Telegram WebApp'
},
type: 'Info',
section: 'settings',
options: {
text: 'This component automatically detects if it\'s running inside a Telegram Mini App and exposes all Telegram WebApp data as internal variables. You can access these variables in your WeWeb project and use them to adapt your UI to the Telegram environment.'
},
},
},
triggerEvents: [
{
name: 'success',
label: {
en: 'On Auth Success',
ru: 'При успешной авторизации'
},
event: {
code: '',
device_id: '',
code_verifier: ''
}
},
{
name: 'error',
label: {
en: 'On Auth Error',
ru: 'При ошибке авторизации'
},
event: {
error: ''
}
},
// Telegram WebApp events
{
name: 'telegramWebAppReady',
label: {
en: 'On Telegram WebApp Ready',
ru: 'При готовности Telegram WebApp'
},
event: {
platform: '',
version: ''
}
},
{
name: 'telegramViewportChanged',
label: {
en: 'On Telegram Viewport Changed',
ru: 'При изменении области просмотра Telegram'
},
event: {
viewportHeight: 0,
isExpanded: false,
safeAreaInset: { top: 0, right: 0, bottom: 0, left: 0 }
}
},
{
name: 'telegramThemeChanged',
label: {
en: 'On Telegram Theme Changed',
ru: 'При изменении темы Telegram'
},
event: {
colorScheme: '',
themeParams: {}
}
},
{
name: 'telegramMainButtonClicked',
label: {
en: 'On Telegram Main Button Clicked',
ru: 'При нажатии на главную кнопку Telegram'
},
event: {}
},
{
name: 'telegramBackButtonClicked',
label: {
en: 'On Telegram Back Button Clicked',
ru: 'При нажатии на кнопку назад Telegram'
},
event: {}
},
{
name: 'telegramSettingsButtonClicked',
label: {
en: 'On Telegram Settings Button Clicked',
ru: 'При нажатии на кнопку настроек Telegram'
},
event: {}
},
{
name: 'telegramPopupClosed',
label: {
en: 'On Telegram Popup Closed',
ru: 'При закрытии всплывающего окна Telegram'
},
event: {
buttonId: ''
}
},
{
name: 'telegramAlertClosed',
label: {
en: 'On Telegram Alert Closed',
ru: 'При закрытии оповещения Telegram'
},
event: {}
},
{
name: 'telegramConfirmClosed',
label: {
en: 'On Telegram Confirm Closed',
ru: 'При закрытии подтверждения Telegram'
},
event: {
confirmed: false
}
},
{
name: 'telegramQrTextReceived',
label: {
en: 'On Telegram QR Text Received',
ru: 'При получении текста QR-кода Telegram'
},
event: {
text: ''
}
},
{
name: 'telegramClipboardTextReceived',
label: {
en: 'On Telegram Clipboard Text Received',
ru: 'При получении текста из буфера обмена Telegram'
},
event: {
text: ''
}
},
{
name: 'telegramInvoiceClosed',
label: {
en: 'On Telegram Invoice Closed',
ru: 'При закрытии счета Telegram'
},
event: {
status: ''
}
}
],
actions: [
// Telegram WebApp actions
{
action: 'telegramExpandApp',
label: {
en: 'Expand Telegram WebApp',
ru: 'Развернуть Telegram WebApp'
}
},
{
action: 'telegramCloseApp',
label: {
en: 'Close Telegram WebApp',
ru: 'Закрыть Telegram WebApp'
}
},
{
action: 'telegramSetupMainButton',
label: {
en: 'Setup Telegram Main Button',
ru: 'Настроить главную кнопку Telegram'
},
args: [
{
name: 'params',
type: 'object',
label: {
en: 'Button Parameters',
ru: 'Параметры кнопки'
}
}
]
},
{
action: 'telegramSetupBackButton',
label: {
en: 'Setup Telegram Back Button',
ru: 'Настроить кнопку назад Telegram'
},
args: [
{
name: 'isVisible',
type: 'boolean',
label: {
en: 'Is Visible',
ru: 'Видимость'
}
}
]
},
{
action: 'telegramShowPopup',
label: {
en: 'Show Telegram Popup',
ru: 'Показать всплывающее окно Telegram'
},
args: [
{
name: 'params',
type: 'object',
label: {
en: 'Popup Parameters',
ru: 'Параметры всплывающего окна'
}
}
]
},
{
action: 'telegramShowAlert',
label: {
en: 'Show Telegram Alert',
ru: 'Показать оповещение Telegram'
},
args: [
{
name: 'message',
type: 'string',
label: {
en: 'Message',
ru: 'Сообщение'
}
}
]
},
{
action: 'telegramShowConfirm',
label: {
en: 'Show Telegram Confirm',
ru: 'Показать подтверждение Telegram'
},
args: [
{
name: 'message',
type: 'string',
label: {
en: 'Message',
ru: 'Сообщение'
}
}
]
},
{
action: 'telegramScanQrCode',
label: {
en: 'Scan QR Code',
ru: 'Сканировать QR-код'
}
},
{
action: 'telegramReadClipboard',
label: {
en: 'Read Clipboard',
ru: 'Прочитать буфер обмена'
}
}
],
};

View File

@ -14,6 +14,7 @@ import element_b783dc65_d528_4f74_8c14_e27c934c39b1 from '@/components/elements/
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';
import element_eb4b02a3_cbe6_47e9_8db5_322da8047160 from '@/components/elements/element-eb4b02a3-cbe6-47e9-8db5-322da8047160/src/wwElement.vue';
// eslint-disable-next-line no-undef
app.component('wwobject-1b1e2173-9b78-42cc-a8ee-a6167caea340', element_1b1e2173_9b78_42cc_a8ee_a6167caea340);
@ -28,6 +29,7 @@ app.component('wwobject-b783dc65-d528-4f74-8c14-e27c934c39b1', element_b783dc65_
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);
app.component('wwobject-eb4b02a3-cbe6-47e9-8db5-322da8047160', element_eb4b02a3_cbe6_47e9_8db5_322da8047160);
// 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';

View File

@ -14,6 +14,7 @@ import element_83d890fb_84f9_4386_b459_fb4be89a8e15 from '@/components/elements/
import element_9256b033_f4e8_4ab4_adce_dac3a940d7f5 from '@/components/elements/element-9256b033-f4e8-4ab4-adce-dac3a940d7f5/src/wwElement.vue';
import element_b783dc65_d528_4f74_8c14_e27c934c39b1 from '@/components/elements/element-b783dc65-d528-4f74-8c14-e27c934c39b1/src/wwElement.vue';
import element_d7904e9d_fc9a_4d80_9e32_728e097879ad from '@/components/elements/element-d7904e9d-fc9a-4d80-9e32-728e097879ad/src/wwElement.vue';
import element_eb4b02a3_cbe6_47e9_8db5_322da8047160 from '@/components/elements/element-eb4b02a3-cbe6-47e9-8db5-322da8047160/src/wwElement.vue';
// eslint-disable-next-line no-undef
app.component('wwobject-1b1e2173-9b78-42cc-a8ee-a6167caea340', element_1b1e2173_9b78_42cc_a8ee_a6167caea340);
@ -28,6 +29,7 @@ app.component('wwobject-83d890fb-84f9-4386-b459-fb4be89a8e15', element_83d890fb_
app.component('wwobject-9256b033-f4e8-4ab4-adce-dac3a940d7f5', element_9256b033_f4e8_4ab4_adce_dac3a940d7f5);
app.component('wwobject-b783dc65-d528-4f74-8c14-e27c934c39b1', element_b783dc65_d528_4f74_8c14_e27c934c39b1);
app.component('wwobject-d7904e9d-fc9a-4d80-9e32-728e097879ad', element_d7904e9d_fc9a_4d80_9e32_728e097879ad);
app.component('wwobject-eb4b02a3-cbe6-47e9-8db5-322da8047160', element_eb4b02a3_cbe6_47e9_8db5_322da8047160);
// 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';

View File

@ -30,12 +30,13 @@ 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 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';
import wwobjecteb4b02a3cbe647e98db5322da8047160 from '@/components/elements/element-eb4b02a3-cbe6-47e9-8db5-322da8047160/ww-config.js';
import wwobject9ecb2cfccef74be8b7363e17a3b7e9ff from '@/components/elements/element-9ecb2cfc-cef7-4be8-b736-3e17a3b7e9ff/ww-config.js';
/* wwFront:end */
export const useComponentBasesStore = defineStore('componentBases', () => {
@ -70,12 +71,13 @@ 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-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' })};
'wwobject-c6c0c00e-49fd-4cb9-bd78-5bc09945721e': getInheritedConfiguration({ ...wwobjectc6c0c00e49fd4cb9bd785bc09945721e, name: 'wwobject-c6c0c00e-49fd-4cb9-bd78-5bc09945721e' }),
'wwobject-eb4b02a3-cbe6-47e9-8db5-322da8047160': getInheritedConfiguration({ ...wwobjecteb4b02a3cbe647e98db5322da8047160, name: 'wwobject-eb4b02a3-cbe6-47e9-8db5-322da8047160' }),
'wwobject-9ecb2cfc-cef7-4be8-b736-3e17a3b7e9ff': getInheritedConfiguration({ ...wwobject9ecb2cfccef74be8b7363e17a3b7e9ff, name: 'wwobject-9ecb2cfc-cef7-4be8-b736-3e17a3b7e9ff' })};
/* wwFront:end */
return {

View File

@ -98,7 +98,7 @@ export const useVariablesStore = defineStore('variables', () => {
}
}
function setValue(variableId, value, { ignoreQuery, path, index, arrayUpdateType, workflowContext } = {}) {
function setValue(variableId, value, { ignoreQuery, path, index, arrayUpdateType, workflowContext, silent } = {}) {
const configuration = this.getConfiguration(variableId);
if (!configuration) return;
if (typeof value === 'string') {
@ -170,6 +170,7 @@ export const useVariablesStore = defineStore('variables', () => {
if (configuration.type === 'object' && path) {
this.values[variableId] = this.values[variableId] || {};
set(this.values[variableId], path, value);
if (!silent)
wwLib.logStore.verbose(`Variable _wwVariable(${variableId}) update`, {
preview: this.values[variableId],
workflowContext,
@ -185,6 +186,7 @@ export const useVariablesStore = defineStore('variables', () => {
finalPath = `${finalPath}.${path}`;
}
set(this.values[variableId], finalPath, value);
if (!silent)
wwLib.logStore.verbose(`Updating partially array variable _wwVariable(${variableId}) `, {
preview: value,
workflowContext,
@ -194,6 +196,7 @@ export const useVariablesStore = defineStore('variables', () => {
}
case 'delete':
this.values[variableId].splice(index, 1);
if (!silent)
wwLib.logStore.verbose(`Deleting item ${index} from array _wwVariable(${variableId})`, {
workflowContext,
type: workflowContext ? 'action' : null,
@ -201,6 +204,7 @@ export const useVariablesStore = defineStore('variables', () => {
break;
case 'insert':
this.values[variableId].splice(index, 0, value);
if (!silent)
wwLib.logStore.verbose(
`Inserting value into array variable at index ${index} _wwVariable(${variableId}) `,
{
@ -212,13 +216,18 @@ export const useVariablesStore = defineStore('variables', () => {
break;
case 'unshift':
this.values[variableId].unshift(value);
wwLib.logStore.verbose(`Removing first element from array variable _wwVariable(${variableId}) `, {
if (!silent)
wwLib.logStore.verbose(
`Removing first element from array variable _wwVariable(${variableId}) `,
{
workflowContext,
type: workflowContext ? 'action' : null,
});
}
);
break;
case 'push':
this.values[variableId].push(value);
if (!silent)
wwLib.logStore.verbose(
`Adding value at the end of the array variable _wwVariable(${variableId}) `,
{
@ -230,6 +239,7 @@ export const useVariablesStore = defineStore('variables', () => {
break;
case 'shift':
this.values[variableId].shift(value);
if (!silent)
wwLib.logStore.verbose(
`Adding value at the start of the array variable _wwVariable(${variableId}) `,
{
@ -241,6 +251,7 @@ export const useVariablesStore = defineStore('variables', () => {
break;
case 'pop':
this.values[variableId].pop(value);
if (!silent)
wwLib.logStore.verbose(`Removing last value of the array variable _wwVariable(${variableId})`, {
workflowContext,
type: workflowContext ? 'action' : null,
@ -249,6 +260,7 @@ export const useVariablesStore = defineStore('variables', () => {
}
} else {
this.values[variableId] = value;
if (!silent)
wwLib.logStore.verbose(`Setting value for _wwVariable(${variableId}) `, {
preview: _.cloneDeep(value),
workflowContext,
@ -257,7 +269,7 @@ export const useVariablesStore = defineStore('variables', () => {
}
if (configuration.isLocalStorage && window.localStorage) {
wwLib.logStore.verbose(`Updating localStorage to synchronize with _wwVariable(${variableId})`);
if (!silent) wwLib.logStore.verbose(`Updating localStorage to synchronize with _wwVariable(${variableId})`);
switch (configuration.type) {
case 'query':
case 'string':

View File

@ -16,7 +16,7 @@ export default {
/**
* @PUBLIC_API
*/
updateValue(variableId, value, { path, index, arrayUpdateType, workflowContext } = {}) {
updateValue(variableId, value, { path, index, arrayUpdateType, workflowContext, silent } = {}) {
const variablesStore = useVariablesStore(wwLib.$pinia);
const variable = variablesStore.getConfiguration(variableId);
try {
@ -32,7 +32,7 @@ export default {
}
value = checkVariableType(variable, value, { path, arrayUpdateType });
variablesStore.setValue(variableId, value, { path, index, arrayUpdateType, workflowContext });
variablesStore.setValue(variableId, value, { path, index, arrayUpdateType, workflowContext, silent });
return value;
} catch (error) {

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=183" />
<link rel="manifest" href="manifest.json?_wwcv=189" />
<meta name="theme-color" content="" />
<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 rel="apple-touch-icon" sizes="48x48" href="images/48-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="72x72" href="images/72-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="96x96" href="images/96-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="128x128" href="images/128-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="144x144" href="images/144-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="152x152" href="images/152-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="192x192" href="images/192-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="256x256" href="images/256-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="384x384" href="images/384-favicon.png?_wwcv=189">
<link rel="apple-touch-icon" sizes="512x512" href="images/512-favicon.png?_wwcv=189">
<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=183" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link href="/fonts/Phosphor/font.css?_wwcv=189" 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>

File diff suppressed because one or more lines are too long