mirror of
https://github.com/umami-software/umami.git
synced 2026-02-23 22:15:35 +01:00
Compare commits
No commits in common. "5213e04f444297b47eb3e5c7d8f5c8d1d68417f9" and "42d0594118b67d916e2c9a1d7e8e50dc7b72a5d9" have entirely different histories.
5213e04f44
...
42d0594118
16 changed files with 160 additions and 452 deletions
91
.gitignore
vendored
91
.gitignore
vendored
|
|
@ -1,46 +1,45 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules
|
node_modules
|
||||||
.pnp
|
.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
/.next
|
/.next
|
||||||
/out
|
/out
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
/public/script.js
|
/public/script.js
|
||||||
/geo
|
/geo
|
||||||
/dist
|
/dist
|
||||||
/generated
|
/generated
|
||||||
/src/generated
|
/src/generated
|
||||||
pm2.yml
|
|
||||||
|
# misc
|
||||||
# misc
|
.DS_Store
|
||||||
.DS_Store
|
.idea
|
||||||
.idea
|
.yarn
|
||||||
.yarn
|
*.iml
|
||||||
*.iml
|
*.log
|
||||||
*.log
|
.vscode
|
||||||
.vscode
|
.tool-versions
|
||||||
.tool-versions
|
|
||||||
|
# debug
|
||||||
# debug
|
npm-debug.log*
|
||||||
npm-debug.log*
|
yarn-debug.log*
|
||||||
yarn-debug.log*
|
yarn-error.log*
|
||||||
yarn-error.log*
|
|
||||||
|
# local env files
|
||||||
# local env files
|
.env
|
||||||
.env
|
.env.*
|
||||||
.env.*
|
*.env.*
|
||||||
*.env.*
|
|
||||||
|
*.dev.yml
|
||||||
*.dev.yml
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ const cloudMode = process.env.CLOUD_MODE || '';
|
||||||
const cloudUrl = process.env.CLOUD_URL || '';
|
const cloudUrl = process.env.CLOUD_URL || '';
|
||||||
const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT || '';
|
const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT || '';
|
||||||
const corsMaxAge = process.env.CORS_MAX_AGE || '';
|
const corsMaxAge = process.env.CORS_MAX_AGE || '';
|
||||||
const defaultCurrency = process.env.DEFAULT_CURRENCY || '';
|
|
||||||
const defaultLocale = process.env.DEFAULT_LOCALE || '';
|
const defaultLocale = process.env.DEFAULT_LOCALE || '';
|
||||||
const forceSSL = process.env.FORCE_SSL || '';
|
const forceSSL = process.env.FORCE_SSL || '';
|
||||||
const frameAncestors = process.env.ALLOWED_FRAME_URLS || '';
|
const frameAncestors = process.env.ALLOWED_FRAME_URLS || '';
|
||||||
|
|
@ -171,7 +170,6 @@ export default {
|
||||||
cloudMode,
|
cloudMode,
|
||||||
cloudUrl,
|
cloudUrl,
|
||||||
currentVersion: pkg.version,
|
currentVersion: pkg.version,
|
||||||
defaultCurrency,
|
|
||||||
defaultLocale,
|
defaultLocale,
|
||||||
},
|
},
|
||||||
basePath,
|
basePath,
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,6 @@
|
||||||
"value": "访问代码"
|
"value": "访问代码"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.account": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "账户"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.action": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "行为"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.actions": [
|
"label.actions": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -47,24 +35,12 @@
|
||||||
"value": "添加描述"
|
"value": "添加描述"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.add-link": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "添加链接"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.add-member": [
|
"label.add-member": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "添加成员"
|
"value": "添加成员"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.add-pixel": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "添加像素"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.add-step": [
|
"label.add-step": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -107,24 +83,12 @@
|
||||||
"value": "所有时间段"
|
"value": "所有时间段"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.analysis": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "分析"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.analytics": [
|
"label.analytics": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "分析"
|
"value": "分析"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.application": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "应用"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.apply": [
|
"label.apply": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -143,12 +107,6 @@
|
||||||
"value": "查看用户如何与您的营销互动,以及是什么促成了转化。"
|
"value": "查看用户如何与您的营销互动,以及是什么促成了转化。"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.audience": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "受众"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.average": [
|
"label.average": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -167,12 +125,6 @@
|
||||||
"value": "之前"
|
"value": "之前"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.behavior": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "行为"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.boards": [
|
"label.boards": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -221,24 +173,12 @@
|
||||||
"value": "修改密码"
|
"value": "修改密码"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.channel": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "渠道"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.channels": [
|
"label.channels": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "渠道"
|
"value": "渠道"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.chart": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "图表"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.cities": [
|
"label.cities": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -263,12 +203,6 @@
|
||||||
"value": "队列"
|
"value": "队列"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.cohorts": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "队列"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.compare": [
|
"label.compare": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -383,12 +317,6 @@
|
||||||
"value": "创建者"
|
"value": "创建者"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.criteria": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "条件"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.currency": [
|
"label.currency": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -491,12 +419,6 @@
|
||||||
"value": "台式机"
|
"value": "台式机"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.destination-url": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "目标URL"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.details": [
|
"label.details": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -533,12 +455,6 @@
|
||||||
"value": "唯一ID"
|
"value": "唯一ID"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.documentation": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "文档"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.does-not-contain": [
|
"label.does-not-contain": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -563,12 +479,6 @@
|
||||||
"value": "域名"
|
"value": "域名"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.download": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "下载"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.dropoff": [
|
"label.dropoff": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -596,7 +506,7 @@
|
||||||
"label.email": [
|
"label.email": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "邮箱"
|
"value": "Email"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.enable-share-url": [
|
"label.enable-share-url": [
|
||||||
|
|
@ -617,12 +527,6 @@
|
||||||
"value": "入口 URL"
|
"value": "入口 URL"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.environment": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "环境"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.event": [
|
"label.event": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -767,12 +671,6 @@
|
||||||
"value": "分组"
|
"value": "分组"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.growth": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "增长"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.hostname": [
|
"label.hostname": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -803,12 +701,6 @@
|
||||||
"value": "通过使用筛选器和划分时间段来更深入地研究数据。"
|
"value": "通过使用筛选器和划分时间段来更深入地研究数据。"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.invalid-url": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "无效URL"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.is": [
|
"label.is": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -971,24 +863,12 @@
|
||||||
"value": "少于等于"
|
"value": "少于等于"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.link": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "链接"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.links": [
|
"label.links": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "链接"
|
"value": "链接"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.location": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "位置"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.login": [
|
"label.login": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -1140,7 +1020,7 @@
|
||||||
"label.online": [
|
"label.online": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "在线"
|
"value": "Online"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.organic-search": [
|
"label.organic-search": [
|
||||||
|
|
@ -1285,12 +1165,6 @@
|
||||||
"value": "路径"
|
"value": "路径"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.pixel": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "像素"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.pixels": [
|
"label.pixels": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -1311,12 +1185,6 @@
|
||||||
"value": " 提供支持"
|
"value": " 提供支持"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.preferences": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "偏好"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.previous": [
|
"label.previous": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -1341,12 +1209,6 @@
|
||||||
"value": "个人资料"
|
"value": "个人资料"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.profiles": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "个人资料"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.properties": [
|
"label.properties": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -1386,7 +1248,7 @@
|
||||||
"label.referral": [
|
"label.referral": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "来源"
|
"value": "Referral"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.referrer": [
|
"label.referrer": [
|
||||||
|
|
@ -1509,24 +1371,6 @@
|
||||||
"value": "保存"
|
"value": "保存"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.save-cohort": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "保存为群组"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.save-segment": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "保存为细分"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.screen": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "屏幕"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.screens": [
|
"label.screens": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -1539,18 +1383,6 @@
|
||||||
"value": "搜索"
|
"value": "搜索"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.segment": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "细分"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.segments": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "细分"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.select": [
|
"label.select": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -1653,24 +1485,6 @@
|
||||||
"value": "总和"
|
"value": "总和"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.support": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "支持"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.switch-account": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "切换账户"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.table": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "表格"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.tablet": [
|
"label.tablet": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -1821,12 +1635,6 @@
|
||||||
"value": "跟踪代码"
|
"value": "跟踪代码"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"label.traffic": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"value": "流量"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"label.transactions": [
|
"label.transactions": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
|
|
@ -2038,7 +1846,7 @@
|
||||||
"message.bad-request": [
|
"message.bad-request": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "请求错误"
|
"value": "Bad request"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"message.collected-data": [
|
"message.collected-data": [
|
||||||
|
|
@ -2138,7 +1946,7 @@
|
||||||
"message.forbidden": [
|
"message.forbidden": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "禁止访问"
|
"value": "Forbidden"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"message.go-to-settings": [
|
"message.go-to-settings": [
|
||||||
|
|
@ -2238,13 +2046,13 @@
|
||||||
"message.not-found": [
|
"message.not-found": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "未找到"
|
"value": "Not found"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"message.nothing-selected": [
|
"message.nothing-selected": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "未选择"
|
"value": "Nothing selected."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"message.page-not-found": [
|
"message.page-not-found": [
|
||||||
|
|
@ -2282,7 +2090,7 @@
|
||||||
"message.sever-error": [
|
"message.sever-error": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "服务器错误"
|
"value": "Server error"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"message.share-url": [
|
"message.share-url": [
|
||||||
|
|
@ -2350,7 +2158,7 @@
|
||||||
"message.unauthorized": [
|
"message.unauthorized": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "未授权"
|
"value": "Unauthorized"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"message.user-deleted": [
|
"message.user-deleted": [
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@
|
||||||
"AD-06": "Sant Julia de Loria",
|
"AD-06": "Sant Julia de Loria",
|
||||||
"AD-07": "Andorra la Vella",
|
"AD-07": "Andorra la Vella",
|
||||||
"AD-08": "Escaldes-Engordany",
|
"AD-08": "Escaldes-Engordany",
|
||||||
"AE-AJ": "Ajman",
|
"AE-AJ": "'Ajman",
|
||||||
"AE-AZ": "Abu Dhabi",
|
"AE-AZ": "Abu Zaby",
|
||||||
"AE-DU": "Dubai",
|
"AE-DU": "Dubayy",
|
||||||
"AE-FU": "Al Fujairah",
|
"AE-FU": "Al Fujayrah",
|
||||||
"AE-RK": "Ras al Khaimah",
|
"AE-RK": "Ra's al Khaymah",
|
||||||
"AE-SH": "Sharjah",
|
"AE-SH": "Ash Shariqah",
|
||||||
"AE-UQ": "Umm al Quwain",
|
"AE-UQ": "Umm al Qaywayn",
|
||||||
"AF-BAL": "Balkh",
|
"AF-BAL": "Balkh",
|
||||||
"AF-BAM": "Bamyan",
|
"AF-BAM": "Bamyan",
|
||||||
"AF-BDG": "Badghis",
|
"AF-BDG": "Badghis",
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,13 @@ import {
|
||||||
Form,
|
Form,
|
||||||
FormField,
|
FormField,
|
||||||
FormSubmitButton,
|
FormSubmitButton,
|
||||||
Grid,
|
|
||||||
Icon,
|
Icon,
|
||||||
Label,
|
Label,
|
||||||
Loading,
|
Loading,
|
||||||
Row,
|
Row,
|
||||||
TextField,
|
TextField,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useConfig, useLinkQuery, useMessages } from '@/components/hooks';
|
import { useConfig, useLinkQuery, useMessages } from '@/components/hooks';
|
||||||
import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
|
import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
|
||||||
import { RefreshCw } from '@/components/icons';
|
import { RefreshCw } from '@/components/icons';
|
||||||
|
|
@ -43,7 +42,7 @@ export function LinkEditForm({
|
||||||
const { linksUrl } = useConfig();
|
const { linksUrl } = useConfig();
|
||||||
const hostUrl = linksUrl || LINKS_URL;
|
const hostUrl = linksUrl || LINKS_URL;
|
||||||
const { data, isLoading } = useLinkQuery(linkId);
|
const { data, isLoading } = useLinkQuery(linkId);
|
||||||
const [defaultSlug] = useState(generateId());
|
const [slug, setSlug] = useState(generateId());
|
||||||
|
|
||||||
const handleSubmit = async (data: any) => {
|
const handleSubmit = async (data: any) => {
|
||||||
await mutateAsync(data, {
|
await mutateAsync(data, {
|
||||||
|
|
@ -56,6 +55,14 @@ export function LinkEditForm({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSlug = () => {
|
||||||
|
const slug = generateId();
|
||||||
|
|
||||||
|
setSlug(slug);
|
||||||
|
|
||||||
|
return slug;
|
||||||
|
};
|
||||||
|
|
||||||
const checkUrl = (url: string) => {
|
const checkUrl = (url: string) => {
|
||||||
if (!isValidUrl(url)) {
|
if (!isValidUrl(url)) {
|
||||||
return formatMessage(labels.invalidUrl);
|
return formatMessage(labels.invalidUrl);
|
||||||
|
|
@ -63,19 +70,19 @@ export function LinkEditForm({
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
setSlug(data.slug);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
if (linkId && isLoading) {
|
if (linkId && isLoading) {
|
||||||
return <Loading placement="absolute" />;
|
return <Loading placement="absolute" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form onSubmit={handleSubmit} error={getErrorMessage(error)} defaultValues={{ slug, ...data }}>
|
||||||
onSubmit={handleSubmit}
|
{({ setValue }) => {
|
||||||
error={getErrorMessage(error)}
|
|
||||||
defaultValues={{ slug: defaultSlug, ...data }}
|
|
||||||
>
|
|
||||||
{({ setValue, watch }) => {
|
|
||||||
const slug = watch('slug');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
@ -94,25 +101,15 @@ export function LinkEditForm({
|
||||||
<TextField placeholder="https://example.com" autoComplete="off" />
|
<TextField placeholder="https://example.com" autoComplete="off" />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<Grid columns="1fr auto" alignItems="end" gap>
|
<FormField
|
||||||
<FormField
|
name="slug"
|
||||||
name="slug"
|
rules={{
|
||||||
label={formatMessage({ id: 'label.slug', defaultMessage: 'Slug' })}
|
required: formatMessage(labels.required),
|
||||||
rules={{
|
}}
|
||||||
required: formatMessage(labels.required),
|
style={{ display: 'none' }}
|
||||||
}}
|
>
|
||||||
>
|
<input type="hidden" />
|
||||||
<TextField autoComplete="off" />
|
</FormField>
|
||||||
</FormField>
|
|
||||||
<Button
|
|
||||||
variant="quiet"
|
|
||||||
onPress={() => setValue('slug', generateId(), { shouldDirty: true })}
|
|
||||||
>
|
|
||||||
<Icon>
|
|
||||||
<RefreshCw />
|
|
||||||
</Icon>
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Column>
|
<Column>
|
||||||
<Label>{formatMessage(labels.link)}</Label>
|
<Label>{formatMessage(labels.link)}</Label>
|
||||||
|
|
@ -124,6 +121,14 @@ export function LinkEditForm({
|
||||||
allowCopy
|
allowCopy
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
variant="quiet"
|
||||||
|
onPress={() => setValue('slug', handleSlug(), { shouldDirty: true })}
|
||||||
|
>
|
||||||
|
<Icon>
|
||||||
|
<RefreshCw />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,9 @@ import { ListTable } from '@/components/metrics/ListTable';
|
||||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||||
import { renderDateLabels } from '@/lib/charts';
|
import { renderDateLabels } from '@/lib/charts';
|
||||||
import { CHART_COLORS, CURRENCY_CONFIG, DEFAULT_CURRENCY } from '@/lib/constants';
|
import { CHART_COLORS } from '@/lib/constants';
|
||||||
import { generateTimeSeries } from '@/lib/date';
|
import { generateTimeSeries } from '@/lib/date';
|
||||||
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
|
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
|
||||||
import { getItem, setItem } from '@/lib/storage';
|
|
||||||
|
|
||||||
export interface RevenueProps {
|
export interface RevenueProps {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
|
|
@ -25,15 +24,7 @@ export interface RevenueProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
||||||
const [currency, setCurrency] = useState(
|
const [currency, setCurrency] = useState('USD');
|
||||||
getItem(CURRENCY_CONFIG) || process.env.defaultCurrency || DEFAULT_CURRENCY,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCurrencyChange = (value: string) => {
|
|
||||||
setCurrency(value);
|
|
||||||
setItem(CURRENCY_CONFIG, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale, dateLocale } = useLocale();
|
const { locale, dateLocale } = useLocale();
|
||||||
const { countryNames } = useCountryNames(locale);
|
const { countryNames } = useCountryNames(locale);
|
||||||
|
|
@ -116,7 +107,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column gap>
|
||||||
<Grid columns="280px" gap>
|
<Grid columns="280px" gap>
|
||||||
<CurrencySelect value={currency} onChange={handleCurrencyChange} />
|
<CurrencySelect value={currency} onChange={setCurrency} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||||
{data && (
|
{data && (
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export function useEventDataValuesQuery(
|
||||||
return useQuery<any>({
|
return useQuery<any>({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
'websites:event-data:values',
|
'websites:event-data:values',
|
||||||
{ websiteId, startAt, endAt, unit, timezone, ...filters, event, propertyName },
|
{ websiteId, event, propertyName, startAt, endAt, unit, timezone, ...filters },
|
||||||
],
|
],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
get(`/websites/${websiteId}/event-data/values`, {
|
get(`/websites/${websiteId}/event-data/values`, {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ export function WebsiteSelect({
|
||||||
renderValue={renderValue}
|
renderValue={renderValue}
|
||||||
listProps={{
|
listProps={{
|
||||||
renderEmptyState: () => <Empty message={formatMessage(messages.noResultsFound)} />,
|
renderEmptyState: () => <Empty message={formatMessage(messages.noResultsFound)} />,
|
||||||
style: { maxHeight: 'calc(42vh - 65px)' },
|
style: { maxHeight: '400px' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ id, name }: any) => <ListItem key={id}>{name}</ListItem>}
|
{({ id, name }: any) => <ListItem key={id}>{name}</ListItem>}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@
|
||||||
"label.event-name": "Име на събитие",
|
"label.event-name": "Име на събитие",
|
||||||
"label.events": "Събития",
|
"label.events": "Събития",
|
||||||
"label.exists": "Съществува",
|
"label.exists": "Съществува",
|
||||||
"label.exit": "URL за изход",
|
"label.exit": "Exit URL",
|
||||||
"label.false": "Грешно",
|
"label.false": "Грешно",
|
||||||
"label.field": "Поле",
|
"label.field": "Поле",
|
||||||
"label.fields": "Полета",
|
"label.fields": "Полета",
|
||||||
|
|
@ -135,7 +135,7 @@
|
||||||
"label.last-days": "Последните {x} дни",
|
"label.last-days": "Последните {x} дни",
|
||||||
"label.last-hours": "Последните {x} часа",
|
"label.last-hours": "Последните {x} часа",
|
||||||
"label.last-months": "Последните {x} месеца",
|
"label.last-months": "Последните {x} месеца",
|
||||||
"label.last-seen": "Последно видяно",
|
"label.last-seen": "Last seen",
|
||||||
"label.leave": "Напусни",
|
"label.leave": "Напусни",
|
||||||
"label.leave-team": "Напусни екип",
|
"label.leave-team": "Напусни екип",
|
||||||
"label.less-than": "По-малко от",
|
"label.less-than": "По-малко от",
|
||||||
|
|
@ -161,7 +161,7 @@
|
||||||
"label.none": "Няма",
|
"label.none": "Няма",
|
||||||
"label.number-of-records": "{x} {x, plural, one {един} other {други}}",
|
"label.number-of-records": "{x} {x, plural, one {един} other {други}}",
|
||||||
"label.ok": "Добре",
|
"label.ok": "Добре",
|
||||||
"label.online": "Онлайн",
|
"label.online": "Online",
|
||||||
"label.organic-search": "Органично търсене",
|
"label.organic-search": "Органично търсене",
|
||||||
"label.organic-shopping": "Органично пазаруване",
|
"label.organic-shopping": "Органично пазаруване",
|
||||||
"label.organic-social": "Органични социални мрежи",
|
"label.organic-social": "Органични социални мрежи",
|
||||||
|
|
@ -185,9 +185,9 @@
|
||||||
"label.paths": "Пътища",
|
"label.paths": "Пътища",
|
||||||
"label.pixels": "Пиксели",
|
"label.pixels": "Пиксели",
|
||||||
"label.powered-by": "Поддържано от {name}",
|
"label.powered-by": "Поддържано от {name}",
|
||||||
"label.previous": "Предишен",
|
"label.previous": "Previous",
|
||||||
"label.previous-period": "Предишен период",
|
"label.previous-period": "Previous period",
|
||||||
"label.previous-year": "Предишна година",
|
"label.previous-year": "Previous year",
|
||||||
"label.profile": "Профил",
|
"label.profile": "Профил",
|
||||||
"label.properties": "Свойства",
|
"label.properties": "Свойства",
|
||||||
"label.property": "Свойство",
|
"label.property": "Свойство",
|
||||||
|
|
@ -211,8 +211,8 @@
|
||||||
"label.reset-website": "Нулирай уебсайт",
|
"label.reset-website": "Нулирай уебсайт",
|
||||||
"label.retention": "Привързване",
|
"label.retention": "Привързване",
|
||||||
"label.retention-description": "Измерете привързаността към вашия уебсайт, като проследявате колко често потребителите се връщат.",
|
"label.retention-description": "Измерете привързаността към вашия уебсайт, като проследявате колко често потребителите се връщат.",
|
||||||
"label.revenue": "Приходи",
|
"label.revenue": "Revenue",
|
||||||
"label.revenue-description": "Прегледайте приходите си във времето.",
|
"label.revenue-description": "Look into your revenue across time.",
|
||||||
"label.role": "Роля",
|
"label.role": "Роля",
|
||||||
"label.run-query": "Изпълни запитване",
|
"label.run-query": "Изпълни запитване",
|
||||||
"label.save": "Запази",
|
"label.save": "Запази",
|
||||||
|
|
@ -260,14 +260,14 @@
|
||||||
"label.total": "Общо",
|
"label.total": "Общо",
|
||||||
"label.total-records": "Общо записи",
|
"label.total-records": "Общо записи",
|
||||||
"label.tracking-code": "Код за проследяване",
|
"label.tracking-code": "Код за проследяване",
|
||||||
"label.transactions": "Транзакции",
|
"label.transactions": "Transactions",
|
||||||
"label.transfer": "Прехвърли",
|
"label.transfer": "Прехвърли",
|
||||||
"label.transfer-website": "Прехвърляне на уебсайт",
|
"label.transfer-website": "Прехвърляне на уебсайт",
|
||||||
"label.true": "Вярно",
|
"label.true": "Вярно",
|
||||||
"label.type": "Вид",
|
"label.type": "Вид",
|
||||||
"label.unique": "Уникален",
|
"label.unique": "Уникален",
|
||||||
"label.unique-visitors": "Уникални посетители",
|
"label.unique-visitors": "Уникални посетители",
|
||||||
"label.uniqueCustomers": "Уникални клиенти",
|
"label.uniqueCustomers": "Unique Customers",
|
||||||
"label.unknown": "Неизвестен",
|
"label.unknown": "Неизвестен",
|
||||||
"label.untitled": "Без заглавие",
|
"label.untitled": "Без заглавие",
|
||||||
"label.update": "Актуализирай",
|
"label.update": "Актуализирай",
|
||||||
|
|
@ -282,7 +282,7 @@
|
||||||
"label.view-only": "Само за преглед",
|
"label.view-only": "Само за преглед",
|
||||||
"label.views": "Прегледи",
|
"label.views": "Прегледи",
|
||||||
"label.views-per-visit": "Прегледи на посещение",
|
"label.views-per-visit": "Прегледи на посещение",
|
||||||
"label.visit-duration": "Продължителност на посещение",
|
"label.visit-duration": "Visit duration",
|
||||||
"label.visitors": "Посетители",
|
"label.visitors": "Посетители",
|
||||||
"label.visits": "Посещения",
|
"label.visits": "Посещения",
|
||||||
"label.website": "Уебсайт",
|
"label.website": "Уебсайт",
|
||||||
|
|
@ -292,8 +292,8 @@
|
||||||
"label.yesterday": "Вчера",
|
"label.yesterday": "Вчера",
|
||||||
"message.action-confirmation": "Въведете {confirmation} в полето по-долу, за да потвърдите.",
|
"message.action-confirmation": "Въведете {confirmation} в полето по-долу, за да потвърдите.",
|
||||||
"message.active-users": "{x} {x, plural, one {активен един} other {активни други}}",
|
"message.active-users": "{x} {x, plural, one {активен един} other {активни други}}",
|
||||||
"message.bad-request": "Невалидна заявка",
|
"message.bad-request": "Bad request",
|
||||||
"message.collected-data": "Събрани данни",
|
"message.collected-data": "Collected data",
|
||||||
"message.confirm-delete": "Сигурни ли сте, че искате да изтриете {target}?",
|
"message.confirm-delete": "Сигурни ли сте, че искате да изтриете {target}?",
|
||||||
"message.confirm-leave": "Сигурни ли сте, че искате да напуснете {target}?",
|
"message.confirm-leave": "Сигурни ли сте, че искате да напуснете {target}?",
|
||||||
"message.confirm-remove": "Сигурни ли сте, че искате да премахнете {target}?",
|
"message.confirm-remove": "Сигурни ли сте, че искате да премахнете {target}?",
|
||||||
|
|
@ -302,7 +302,7 @@
|
||||||
"message.delete-website-warning": "Всички данни за уебсайта ще бъдат изтрити.",
|
"message.delete-website-warning": "Всички данни за уебсайта ще бъдат изтрити.",
|
||||||
"message.error": "Възникна грешка.",
|
"message.error": "Възникна грешка.",
|
||||||
"message.event-log": "{event} на {url}",
|
"message.event-log": "{event} на {url}",
|
||||||
"message.forbidden": "Забранено",
|
"message.forbidden": "Forbidden",
|
||||||
"message.go-to-settings": "Отидете в настройките",
|
"message.go-to-settings": "Отидете в настройките",
|
||||||
"message.incorrect-username-password": "Неправилно потребителско име и/или парола.",
|
"message.incorrect-username-password": "Неправилно потребителско име и/или парола.",
|
||||||
"message.invalid-domain": "Невалиден домейн. Не включвайте http/https.",
|
"message.invalid-domain": "Невалиден домейн. Не включвайте http/https.",
|
||||||
|
|
@ -316,13 +316,13 @@
|
||||||
"message.no-teams": "Няма създадени екипи.",
|
"message.no-teams": "Няма създадени екипи.",
|
||||||
"message.no-users": "Няма потребители.",
|
"message.no-users": "Няма потребители.",
|
||||||
"message.no-websites-configured": "Нямате конфигурирани уебсайтове.",
|
"message.no-websites-configured": "Нямате конфигурирани уебсайтове.",
|
||||||
"message.not-found": "Не е намерено",
|
"message.not-found": "Not found",
|
||||||
"message.nothing-selected": "Няма избрано.",
|
"message.nothing-selected": "Nothing selected.",
|
||||||
"message.page-not-found": "Страницата не е намерена",
|
"message.page-not-found": "Страницата не е намерена",
|
||||||
"message.reset-website": "За да нулирате този уебсайт, въведете {confirmation} в полето по-долу, за да потвърдите.",
|
"message.reset-website": "За да нулирате този уебсайт, въведете {confirmation} в полето по-долу, за да потвърдите.",
|
||||||
"message.reset-website-warning": "Всички статистически данни за този уебсайт ще бъдат изтрити, но вашите настройки ще останат непроменени.",
|
"message.reset-website-warning": "Всички статистически данни за този уебсайт ще бъдат изтрити, но вашите настройки ще останат непроменени.",
|
||||||
"message.saved": "Запазено.",
|
"message.saved": "Запазено.",
|
||||||
"message.sever-error": "Сървърна грешка",
|
"message.sever-error": "Server error",
|
||||||
"message.share-url": "Статистиката за вашия уебсайт е публично достъпна на следния URL адрес:",
|
"message.share-url": "Статистиката за вашия уебсайт е публично достъпна на следния URL адрес:",
|
||||||
"message.team-already-member": "Вече сте член на екипа.",
|
"message.team-already-member": "Вече сте член на екипа.",
|
||||||
"message.team-not-found": "Екипът не е намерен.",
|
"message.team-not-found": "Екипът не е намерен.",
|
||||||
|
|
@ -332,7 +332,7 @@
|
||||||
"message.transfer-user-website-to-team": "Изберете екипът на който да бъде прехвърлен уебсайта.",
|
"message.transfer-user-website-to-team": "Изберете екипът на който да бъде прехвърлен уебсайта.",
|
||||||
"message.transfer-website": "Прехвърли собствеността на уебсайта към вашия акаунт или към друг екип.",
|
"message.transfer-website": "Прехвърли собствеността на уебсайта към вашия акаунт или към друг екип.",
|
||||||
"message.triggered-event": "Активирано събитие",
|
"message.triggered-event": "Активирано събитие",
|
||||||
"message.unauthorized": "Неоторизиран достъп",
|
"message.unauthorized": "Unauthorized",
|
||||||
"message.user-deleted": "Потребителят е изтрит.",
|
"message.user-deleted": "Потребителят е изтрит.",
|
||||||
"message.viewed-page": "Страницата е видяна",
|
"message.viewed-page": "Страницата е видяна",
|
||||||
"message.visitor-log": "Посетител от {country}, използващ {browser} на {os} {device}"
|
"message.visitor-log": "Посетител от {country}, използващ {browser} на {os} {device}"
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
{
|
{
|
||||||
"label.access-code": "访问代码",
|
"label.access-code": "访问代码",
|
||||||
"label.account": "账户",
|
|
||||||
"label.action": "行为",
|
|
||||||
"label.actions": "用户行为",
|
"label.actions": "用户行为",
|
||||||
"label.activity": "活动日志",
|
"label.activity": "活动日志",
|
||||||
"label.add": "添加",
|
"label.add": "添加",
|
||||||
"label.add-board": "添加看板",
|
"label.add-board": "添加看板",
|
||||||
"label.add-description": "添加描述",
|
"label.add-description": "添加描述",
|
||||||
"label.add-link": "添加链接",
|
|
||||||
"label.add-member": "添加成员",
|
"label.add-member": "添加成员",
|
||||||
"label.add-pixel": "添加像素",
|
|
||||||
"label.add-step": "添加步骤",
|
"label.add-step": "添加步骤",
|
||||||
"label.add-website": "添加网站",
|
"label.add-website": "添加网站",
|
||||||
"label.admin": "管理员",
|
"label.admin": "管理员",
|
||||||
|
|
@ -17,13 +13,10 @@
|
||||||
"label.after": "之后",
|
"label.after": "之后",
|
||||||
"label.all": "所有",
|
"label.all": "所有",
|
||||||
"label.all-time": "所有时间段",
|
"label.all-time": "所有时间段",
|
||||||
"label.analysis": "分析",
|
|
||||||
"label.analytics": "分析",
|
"label.analytics": "分析",
|
||||||
"label.application": "应用",
|
|
||||||
"label.apply": "应用",
|
"label.apply": "应用",
|
||||||
"label.attribution": "归因",
|
"label.attribution": "归因",
|
||||||
"label.attribution-description": "查看用户如何与您的营销互动,以及是什么促成了转化。",
|
"label.attribution-description": "查看用户如何与您的营销互动,以及是什么促成了转化。",
|
||||||
"label.audience": "受众",
|
|
||||||
"label.average": "平均",
|
"label.average": "平均",
|
||||||
"label.back": "返回",
|
"label.back": "返回",
|
||||||
"label.before": "之前",
|
"label.before": "之前",
|
||||||
|
|
@ -36,14 +29,11 @@
|
||||||
"label.campaigns": "活动",
|
"label.campaigns": "活动",
|
||||||
"label.cancel": "取消",
|
"label.cancel": "取消",
|
||||||
"label.change-password": "修改密码",
|
"label.change-password": "修改密码",
|
||||||
"label.channel": "渠道",
|
|
||||||
"label.channels": "渠道",
|
"label.channels": "渠道",
|
||||||
"label.chart": "图表",
|
|
||||||
"label.cities": "市/县",
|
"label.cities": "市/县",
|
||||||
"label.city": "市/县",
|
"label.city": "市/县",
|
||||||
"label.clear-all": "清除全部",
|
"label.clear-all": "清除全部",
|
||||||
"label.cohort": "队列",
|
"label.cohort": "队列",
|
||||||
"label.cohorts": "队列",
|
|
||||||
"label.compare": "比较",
|
"label.compare": "比较",
|
||||||
"label.compare-dates": "比较日期",
|
"label.compare-dates": "比较日期",
|
||||||
"label.confirm": "确认",
|
"label.confirm": "确认",
|
||||||
|
|
@ -63,7 +53,6 @@
|
||||||
"label.create-user": "创建用户",
|
"label.create-user": "创建用户",
|
||||||
"label.created": "已创建",
|
"label.created": "已创建",
|
||||||
"label.created-by": "创建者",
|
"label.created-by": "创建者",
|
||||||
"label.criteria": "条件",
|
|
||||||
"label.currency": "货币",
|
"label.currency": "货币",
|
||||||
"label.current": "当前",
|
"label.current": "当前",
|
||||||
"label.current-password": "当前密码",
|
"label.current-password": "当前密码",
|
||||||
|
|
@ -81,28 +70,24 @@
|
||||||
"label.delete-website": "删除网站",
|
"label.delete-website": "删除网站",
|
||||||
"label.description": "描述",
|
"label.description": "描述",
|
||||||
"label.desktop": "台式机",
|
"label.desktop": "台式机",
|
||||||
"label.destination-url": "目标URL",
|
|
||||||
"label.details": "详细信息",
|
"label.details": "详细信息",
|
||||||
"label.device": "设备",
|
"label.device": "设备",
|
||||||
"label.devices": "设备",
|
"label.devices": "设备",
|
||||||
"label.direct": "直接",
|
"label.direct": "直接",
|
||||||
"label.dismiss": "关闭",
|
"label.dismiss": "关闭",
|
||||||
"label.distinct-id": "唯一ID",
|
"label.distinct-id": "唯一ID",
|
||||||
"label.documentation": "文档",
|
|
||||||
"label.does-not-contain": "不包含",
|
"label.does-not-contain": "不包含",
|
||||||
"label.does-not-include": "不包括",
|
"label.does-not-include": "不包括",
|
||||||
"label.doest-not-exist": "不存在",
|
"label.doest-not-exist": "不存在",
|
||||||
"label.domain": "域名",
|
"label.domain": "域名",
|
||||||
"label.download": "下载",
|
|
||||||
"label.dropoff": "丢弃",
|
"label.dropoff": "丢弃",
|
||||||
"label.edit": "编辑",
|
"label.edit": "编辑",
|
||||||
"label.edit-dashboard": "编辑仪表盘",
|
"label.edit-dashboard": "编辑仪表盘",
|
||||||
"label.edit-member": "编辑成员",
|
"label.edit-member": "编辑成员",
|
||||||
"label.email": "邮箱",
|
"label.email": "Email",
|
||||||
"label.enable-share-url": "启用共享链接",
|
"label.enable-share-url": "启用共享链接",
|
||||||
"label.end-step": "结束步骤",
|
"label.end-step": "结束步骤",
|
||||||
"label.entry": "入口 URL",
|
"label.entry": "入口 URL",
|
||||||
"label.environment": "环境",
|
|
||||||
"label.event": "事件",
|
"label.event": "事件",
|
||||||
"label.event-data": "事件数据",
|
"label.event-data": "事件数据",
|
||||||
"label.event-name": "事件名称",
|
"label.event-name": "事件名称",
|
||||||
|
|
@ -127,13 +112,11 @@
|
||||||
"label.greater-than": "大于",
|
"label.greater-than": "大于",
|
||||||
"label.greater-than-equals": "大于或等于",
|
"label.greater-than-equals": "大于或等于",
|
||||||
"label.grouped": "分组",
|
"label.grouped": "分组",
|
||||||
"label.growth": "增长",
|
|
||||||
"label.hostname": "主机名",
|
"label.hostname": "主机名",
|
||||||
"label.includes": "包括",
|
"label.includes": "包括",
|
||||||
"label.insight": "洞察",
|
"label.insight": "洞察",
|
||||||
"label.insights": "见解",
|
"label.insights": "见解",
|
||||||
"label.insights-description": "通过使用筛选器和划分时间段来更深入地研究数据。",
|
"label.insights-description": "通过使用筛选器和划分时间段来更深入地研究数据。",
|
||||||
"label.invalid-url": "无效URL",
|
|
||||||
"label.is": "等于",
|
"label.is": "等于",
|
||||||
"label.is-false": "否",
|
"label.is-false": "否",
|
||||||
"label.is-not": "不等于",
|
"label.is-not": "不等于",
|
||||||
|
|
@ -157,9 +140,7 @@
|
||||||
"label.leave-team": "离开团队",
|
"label.leave-team": "离开团队",
|
||||||
"label.less-than": "少于",
|
"label.less-than": "少于",
|
||||||
"label.less-than-equals": "少于等于",
|
"label.less-than-equals": "少于等于",
|
||||||
"label.link": "链接",
|
|
||||||
"label.links": "链接",
|
"label.links": "链接",
|
||||||
"label.location": "位置",
|
|
||||||
"label.login": "登录",
|
"label.login": "登录",
|
||||||
"label.logout": "退出",
|
"label.logout": "退出",
|
||||||
"label.manage": "管理",
|
"label.manage": "管理",
|
||||||
|
|
@ -180,7 +161,7 @@
|
||||||
"label.none": "无",
|
"label.none": "无",
|
||||||
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
||||||
"label.ok": "好的",
|
"label.ok": "好的",
|
||||||
"label.online": "在线",
|
"label.online": "Online",
|
||||||
"label.organic-search": "自然搜索",
|
"label.organic-search": "自然搜索",
|
||||||
"label.organic-shopping": "自然购物",
|
"label.organic-shopping": "自然购物",
|
||||||
"label.organic-social": "自然社交",
|
"label.organic-social": "自然社交",
|
||||||
|
|
@ -202,22 +183,19 @@
|
||||||
"label.password": "密码",
|
"label.password": "密码",
|
||||||
"label.path": "路径",
|
"label.path": "路径",
|
||||||
"label.paths": "路径",
|
"label.paths": "路径",
|
||||||
"label.pixel": "像素",
|
|
||||||
"label.pixels": "像素",
|
"label.pixels": "像素",
|
||||||
"label.powered-by": "由 {name} 提供支持",
|
"label.powered-by": "由 {name} 提供支持",
|
||||||
"label.preferences": "偏好",
|
|
||||||
"label.previous": "先前",
|
"label.previous": "先前",
|
||||||
"label.previous-period": "上一时期",
|
"label.previous-period": "上一时期",
|
||||||
"label.previous-year": "上一年",
|
"label.previous-year": "上一年",
|
||||||
"label.profile": "个人资料",
|
"label.profile": "个人资料",
|
||||||
"label.profiles": "个人资料",
|
|
||||||
"label.properties": "属性",
|
"label.properties": "属性",
|
||||||
"label.property": "属性",
|
"label.property": "属性",
|
||||||
"label.queries": "查询",
|
"label.queries": "查询",
|
||||||
"label.query": "查询",
|
"label.query": "查询",
|
||||||
"label.query-parameters": "查询参数",
|
"label.query-parameters": "查询参数",
|
||||||
"label.realtime": "实时",
|
"label.realtime": "实时",
|
||||||
"label.referral": "来源",
|
"label.referral": "Referral",
|
||||||
"label.referrer": "来源",
|
"label.referrer": "来源",
|
||||||
"label.referrers": "来源域名",
|
"label.referrers": "来源域名",
|
||||||
"label.refresh": "刷新",
|
"label.refresh": "刷新",
|
||||||
|
|
@ -238,13 +216,8 @@
|
||||||
"label.role": "角色",
|
"label.role": "角色",
|
||||||
"label.run-query": "查询",
|
"label.run-query": "查询",
|
||||||
"label.save": "保存",
|
"label.save": "保存",
|
||||||
"label.save-cohort": "保存为群组",
|
|
||||||
"label.save-segment": "保存为细分",
|
|
||||||
"label.screen": "屏幕",
|
|
||||||
"label.screens": "屏幕尺寸",
|
"label.screens": "屏幕尺寸",
|
||||||
"label.search": "搜索",
|
"label.search": "搜索",
|
||||||
"label.segment": "细分",
|
|
||||||
"label.segments": "细分",
|
|
||||||
"label.select": "选择",
|
"label.select": "选择",
|
||||||
"label.select-date": "选择日期",
|
"label.select-date": "选择日期",
|
||||||
"label.select-filter": "选择筛选器",
|
"label.select-filter": "选择筛选器",
|
||||||
|
|
@ -262,9 +235,6 @@
|
||||||
"label.start-step": "开始步骤",
|
"label.start-step": "开始步骤",
|
||||||
"label.steps": "步骤",
|
"label.steps": "步骤",
|
||||||
"label.sum": "总和",
|
"label.sum": "总和",
|
||||||
"label.support": "支持",
|
|
||||||
"label.switch-account": "切换账户",
|
|
||||||
"label.table": "表格",
|
|
||||||
"label.tablet": "平板",
|
"label.tablet": "平板",
|
||||||
"label.tag": "标签",
|
"label.tag": "标签",
|
||||||
"label.tags": "标签",
|
"label.tags": "标签",
|
||||||
|
|
@ -290,7 +260,6 @@
|
||||||
"label.total": "总数",
|
"label.total": "总数",
|
||||||
"label.total-records": "总记录数",
|
"label.total-records": "总记录数",
|
||||||
"label.tracking-code": "跟踪代码",
|
"label.tracking-code": "跟踪代码",
|
||||||
"label.traffic": "流量",
|
|
||||||
"label.transactions": "交易",
|
"label.transactions": "交易",
|
||||||
"label.transfer": "转移",
|
"label.transfer": "转移",
|
||||||
"label.transfer-website": "转移网站",
|
"label.transfer-website": "转移网站",
|
||||||
|
|
@ -323,7 +292,7 @@
|
||||||
"label.yesterday": "昨天",
|
"label.yesterday": "昨天",
|
||||||
"message.action-confirmation": "请在下方输入框中输入 {confirmation} 以确认操作。",
|
"message.action-confirmation": "请在下方输入框中输入 {confirmation} 以确认操作。",
|
||||||
"message.active-users": "当前在线 {x} 位访客",
|
"message.active-users": "当前在线 {x} 位访客",
|
||||||
"message.bad-request": "请求错误",
|
"message.bad-request": "Bad request",
|
||||||
"message.collected-data": "已收集的数据",
|
"message.collected-data": "已收集的数据",
|
||||||
"message.confirm-delete": "你确定要删除 {target} 吗?",
|
"message.confirm-delete": "你确定要删除 {target} 吗?",
|
||||||
"message.confirm-leave": "你确定要离开 {target} 吗?",
|
"message.confirm-leave": "你确定要离开 {target} 吗?",
|
||||||
|
|
@ -333,7 +302,7 @@
|
||||||
"message.delete-website-warning": "所有相关数据将会被删除。",
|
"message.delete-website-warning": "所有相关数据将会被删除。",
|
||||||
"message.error": "发生错误。",
|
"message.error": "发生错误。",
|
||||||
"message.event-log": "{url} 上的 {event}",
|
"message.event-log": "{url} 上的 {event}",
|
||||||
"message.forbidden": "禁止访问",
|
"message.forbidden": "Forbidden",
|
||||||
"message.go-to-settings": "去设置",
|
"message.go-to-settings": "去设置",
|
||||||
"message.incorrect-username-password": "用户名或密码不正确。",
|
"message.incorrect-username-password": "用户名或密码不正确。",
|
||||||
"message.invalid-domain": "无效域名",
|
"message.invalid-domain": "无效域名",
|
||||||
|
|
@ -347,13 +316,13 @@
|
||||||
"message.no-teams": "您尚未创建任何团队。",
|
"message.no-teams": "您尚未创建任何团队。",
|
||||||
"message.no-users": "暂无用户。",
|
"message.no-users": "暂无用户。",
|
||||||
"message.no-websites-configured": "你还没有设置任何网站。",
|
"message.no-websites-configured": "你还没有设置任何网站。",
|
||||||
"message.not-found": "未找到",
|
"message.not-found": "Not found",
|
||||||
"message.nothing-selected": "未选择",
|
"message.nothing-selected": "Nothing selected.",
|
||||||
"message.page-not-found": "页面未找到。",
|
"message.page-not-found": "页面未找到。",
|
||||||
"message.reset-website": "如确定要重置该网站,请在下面输入 {confirmation} 以确认。",
|
"message.reset-website": "如确定要重置该网站,请在下面输入 {confirmation} 以确认。",
|
||||||
"message.reset-website-warning": "此网站的所有统计数据将被删除,但您的跟踪代码将保持不变。",
|
"message.reset-website-warning": "此网站的所有统计数据将被删除,但您的跟踪代码将保持不变。",
|
||||||
"message.saved": "保存成功。",
|
"message.saved": "保存成功。",
|
||||||
"message.sever-error": "服务器错误",
|
"message.sever-error": "Server error",
|
||||||
"message.share-url": "这是 {target} 的共享链接。",
|
"message.share-url": "这是 {target} 的共享链接。",
|
||||||
"message.team-already-member": "你已是该团队的成员。",
|
"message.team-already-member": "你已是该团队的成员。",
|
||||||
"message.team-not-found": "未找到团队。",
|
"message.team-not-found": "未找到团队。",
|
||||||
|
|
@ -363,7 +332,7 @@
|
||||||
"message.transfer-user-website-to-team": "选择要转移此网站的团队。",
|
"message.transfer-user-website-to-team": "选择要转移此网站的团队。",
|
||||||
"message.transfer-website": "将网站所有权转移到您的账户或其他团队。",
|
"message.transfer-website": "将网站所有权转移到您的账户或其他团队。",
|
||||||
"message.triggered-event": "触发事件",
|
"message.triggered-event": "触发事件",
|
||||||
"message.unauthorized": "未授权",
|
"message.unauthorized": "Unauthorized",
|
||||||
"message.user-deleted": "用户已删除。",
|
"message.user-deleted": "用户已删除。",
|
||||||
"message.viewed-page": "已浏览页面",
|
"message.viewed-page": "已浏览页面",
|
||||||
"message.visitor-log": "来自 {country} 的访客在搭载 {os} 的 {device} 上使用 {browser} 浏览器进行访问。"
|
"message.visitor-log": "来自 {country} 的访客在搭载 {os} 的 {device} 上使用 {browser} 浏览器进行访问。"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ export const LOCALE_CONFIG = 'umami.locale';
|
||||||
export const TIMEZONE_CONFIG = 'umami.timezone';
|
export const TIMEZONE_CONFIG = 'umami.timezone';
|
||||||
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
||||||
export const THEME_CONFIG = 'umami.theme';
|
export const THEME_CONFIG = 'umami.theme';
|
||||||
export const CURRENCY_CONFIG = 'umami.currency';
|
|
||||||
export const DASHBOARD_CONFIG = 'umami.dashboard';
|
export const DASHBOARD_CONFIG = 'umami.dashboard';
|
||||||
export const LAST_TEAM_CONFIG = 'umami.last-team';
|
export const LAST_TEAM_CONFIG = 'umami.last-team';
|
||||||
export const VERSION_CHECK = 'umami.version-check';
|
export const VERSION_CHECK = 'umami.version-check';
|
||||||
|
|
@ -26,7 +25,6 @@ export const DEFAULT_WEBSITE_LIMIT = 10;
|
||||||
export const DEFAULT_RESET_DATE = '2000-01-01';
|
export const DEFAULT_RESET_DATE = '2000-01-01';
|
||||||
export const DEFAULT_PAGE_SIZE = 20;
|
export const DEFAULT_PAGE_SIZE = 20;
|
||||||
export const DEFAULT_DATE_COMPARE = 'prev';
|
export const DEFAULT_DATE_COMPARE = 'prev';
|
||||||
export const DEFAULT_CURRENCY = 'USD';
|
|
||||||
|
|
||||||
export const REALTIME_RANGE = 30;
|
export const REALTIME_RANGE = 30;
|
||||||
export const REALTIME_INTERVAL = 10000;
|
export const REALTIME_INTERVAL = 10000;
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,6 @@ const PROVIDER_HEADERS = [
|
||||||
regionHeader: 'cloudfront-viewer-country-region',
|
regionHeader: 'cloudfront-viewer-country-region',
|
||||||
cityHeader: 'cloudfront-viewer-city',
|
cityHeader: 'cloudfront-viewer-city',
|
||||||
},
|
},
|
||||||
// EdgeOne headers (requires custom request headers in Rule Priorities, see: https://edgeone.ai/document/46151)
|
|
||||||
{
|
|
||||||
countryHeader: 'eo-ipcountry',
|
|
||||||
regionHeader: 'eo-region-code',
|
|
||||||
cityHeader: 'eo-ipcity',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getDevice(userAgent: string, screen: string = '') {
|
export function getDevice(userAgent: string, screen: string = '') {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { DEFAULT_CURRENCY } from './constants';
|
|
||||||
|
|
||||||
export function parseTime(val: number) {
|
export function parseTime(val: number) {
|
||||||
const days = ~~(val / 86400);
|
const days = ~~(val / 86400);
|
||||||
const hours = ~~(val / 3600) - days * 24;
|
const hours = ~~(val / 3600) - days * 24;
|
||||||
|
|
@ -96,7 +94,7 @@ export function formatCurrency(value: number, currency: string, locale = 'en-US'
|
||||||
// Fallback to default currency format if an error occurs
|
// Fallback to default currency format if an error occurs
|
||||||
formattedValue = new Intl.NumberFormat(locale, {
|
formattedValue = new Intl.NumberFormat(locale, {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: DEFAULT_CURRENCY,
|
currency: 'USD',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import ipaddr from 'ipaddr.js';
|
|
||||||
|
|
||||||
export const IP_ADDRESS_HEADERS = [
|
export const IP_ADDRESS_HEADERS = [
|
||||||
'true-client-ip', // CDN
|
'true-client-ip', // CDN
|
||||||
'cf-connecting-ip', // Cloudflare
|
'cf-connecting-ip', // Cloudflare
|
||||||
|
|
@ -15,87 +13,35 @@ export const IP_ADDRESS_HEADERS = [
|
||||||
'x-forwarded',
|
'x-forwarded',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize IP strings to a canonical form:
|
|
||||||
* - strips IPv4-mapped IPv6 (e.g. ::ffff:192.0.2.1 -> 192.0.2.1)
|
|
||||||
* - keeps valid IPv4/IPv6 as-is (canonically formatted by ipaddr.js)
|
|
||||||
*/
|
|
||||||
function normalizeIp(ip?: string | null) {
|
|
||||||
if (!ip) return ip;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsed = ipaddr.parse(ip);
|
|
||||||
|
|
||||||
if (parsed.kind() === 'ipv6' && (parsed as ipaddr.IPv6).isIPv4MappedAddress()) {
|
|
||||||
return (parsed as ipaddr.IPv6).toIPv4Address().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed.toString();
|
|
||||||
} catch {
|
|
||||||
// Fallback: return original if parsing fails
|
|
||||||
return ip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveIp(ip?: string | null) {
|
|
||||||
if (!ip) return ip;
|
|
||||||
|
|
||||||
// First, try as-is
|
|
||||||
const normalized = normalizeIp(ip);
|
|
||||||
try {
|
|
||||||
ipaddr.parse(normalized);
|
|
||||||
return normalized;
|
|
||||||
} catch {
|
|
||||||
// try stripping port (handles IPv4:port; leaves IPv6 intact)
|
|
||||||
const stripped = stripPort(ip);
|
|
||||||
if (stripped !== ip) {
|
|
||||||
const normalizedStripped = normalizeIp(stripped);
|
|
||||||
try {
|
|
||||||
ipaddr.parse(normalizedStripped);
|
|
||||||
return normalizedStripped;
|
|
||||||
} catch {
|
|
||||||
return normalizedStripped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getIpAddress(headers: Headers) {
|
export function getIpAddress(headers: Headers) {
|
||||||
const customHeader = process.env.CLIENT_IP_HEADER;
|
const customHeader = process.env.CLIENT_IP_HEADER;
|
||||||
|
|
||||||
if (customHeader && headers.get(customHeader)) {
|
if (customHeader && headers.get(customHeader)) {
|
||||||
return resolveIp(headers.get(customHeader));
|
return headers.get(customHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = IP_ADDRESS_HEADERS.find(name => headers.get(name));
|
const header = IP_ADDRESS_HEADERS.find(name => {
|
||||||
if (!header) {
|
return headers.get(name);
|
||||||
return undefined;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const ip = headers.get(header);
|
const ip = headers.get(header);
|
||||||
|
|
||||||
if (header === 'x-forwarded-for') {
|
if (header === 'x-forwarded-for') {
|
||||||
return resolveIp(ip?.split(',')?.[0]?.trim());
|
return ip?.split(',')?.[0]?.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header === 'forwarded') {
|
if (header === 'forwarded') {
|
||||||
const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/);
|
const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
return resolveIp(match[1]);
|
return match[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolveIp(ip);
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stripPort(ip?: string | null) {
|
export function stripPort(ip: string) {
|
||||||
if (!ip) {
|
|
||||||
return ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ip.startsWith('[')) {
|
if (ip.startsWith('[')) {
|
||||||
const endBracket = ip.indexOf(']');
|
const endBracket = ip.indexOf(']');
|
||||||
if (endBracket !== -1) {
|
if (endBracket !== -1) {
|
||||||
|
|
|
||||||
|
|
@ -46,23 +46,31 @@ export async function relationalQuery({
|
||||||
createdAt,
|
createdAt,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const existing = await client.sessionData.findMany({
|
||||||
|
where: {
|
||||||
|
sessionId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
sessionId: true,
|
||||||
|
dataKey: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
for (const data of flattenedData) {
|
for (const data of flattenedData) {
|
||||||
const { sessionId, dataKey, ...props } = data;
|
const { sessionId, dataKey, ...props } = data;
|
||||||
|
const record = existing.find(e => e.sessionId === sessionId && e.dataKey === dataKey);
|
||||||
|
|
||||||
// Try to update existing record using compound where clause
|
if (record) {
|
||||||
// This is safer than using id from a previous query due to race conditions
|
await client.sessionData.update({
|
||||||
const updateResult = await client.sessionData.updateMany({
|
where: {
|
||||||
where: {
|
id: record.id,
|
||||||
sessionId,
|
},
|
||||||
dataKey,
|
data: {
|
||||||
},
|
...props,
|
||||||
data: {
|
},
|
||||||
...props,
|
});
|
||||||
},
|
} else {
|
||||||
});
|
|
||||||
|
|
||||||
// If no record was updated, create a new one
|
|
||||||
if (updateResult.count === 0) {
|
|
||||||
await client.sessionData.create({
|
await client.sessionData.create({
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,7 @@
|
||||||
if (!currentScript) return;
|
if (!currentScript) return;
|
||||||
|
|
||||||
const { hostname, href, origin } = location;
|
const { hostname, href, origin } = location;
|
||||||
|
const localStorage = href.startsWith('data:') ? undefined : window.localStorage;
|
||||||
let localStorage;
|
|
||||||
try {
|
|
||||||
localStorage = href.startsWith('data:') ? undefined : window.localStorage;
|
|
||||||
} catch {
|
|
||||||
/* (DOMException) SecurityError: Access is denied for this document. */
|
|
||||||
}
|
|
||||||
|
|
||||||
const _data = 'data-';
|
const _data = 'data-';
|
||||||
const _false = 'false';
|
const _false = 'false';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue