Skip to content

Commit 9543fd6

Browse files
github-actions[bot]caoxing9tea-artistboris-wJocky-Teable
committed
[sync] fix: improve sonner toast layout T5582
Synced from teableio/teable-ee@648038f Co-authored-by: Aries X <caoxing9@gmail.com> Co-authored-by: Bieber <artist@teable.io> Co-authored-by: Boris <boris2code@outlook.com> Co-authored-by: Jocky Zhou <jocky@teable.ai> Co-authored-by: Jun Lu <hammond@teable.io> Co-authored-by: Pengap <penganpingprivte@gmail.com> Co-authored-by: SkyHuang <sky.huang.fe@gmail.com> Co-authored-by: Uno <uno@teable.ai> Co-authored-by: nichenqin <nichenqin@hotmail.com>
1 parent 498e255 commit 9543fd6

983 files changed

Lines changed: 86502 additions & 10088 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker-push.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ jobs:
101101

102102
- uses: actions/setup-node@v4
103103
with:
104-
node-version: 22.18.0
104+
node-version: 22.22.3
105105

106106
- name: Set up Docker Buildx
107107
uses: docker/setup-buildx-action@v3

.github/workflows/integration-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
strategy:
2323
fail-fast: false
2424
matrix:
25-
node-version: [22.18.0]
25+
node-version: [22.22.3]
2626
runtime:
2727
- mode: v1
2828
force-v2-all: ''

.github/workflows/linting.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818

1919
strategy:
2020
matrix:
21-
node-version: [22.18.0]
21+
node-version: [22.22.3]
2222

2323
steps:
2424
- uses: actions/checkout@v4

.github/workflows/manual-preview.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666

6767
- uses: actions/setup-node@v4
6868
with:
69-
node-version: 22.18.0
69+
node-version: 22.22.3
7070
- name: ⚙️ Install zx
7171
run: npm install -g zx
7272

.github/workflows/publish-release.yml

Lines changed: 3 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ on:
1111
description: 'Final English release note in Markdown'
1212
required: true
1313
type: string
14-
media_sources:
15-
description: 'JSON map from original media URL to downloadable source metadata'
16-
required: false
17-
default: '{}'
18-
type: string
1914
publish_latest:
2015
description: 'Whether GitHub should mark this release as latest'
2116
required: false
@@ -36,20 +31,13 @@ jobs:
3631
ref: develop
3732
fetch-depth: 0
3833

39-
- name: Install FFmpeg when videos are present
40-
if: contains(inputs.release_note, '.mp4') || contains(inputs.release_note, '.webm') || contains(inputs.release_note, '.mov') || contains(inputs.release_note, '.m4v') || contains(inputs.release_note, '.ogg')
41-
run: |
42-
sudo apt-get update
43-
sudo apt-get install -y ffmpeg
44-
4534
- name: Build release body
4635
env:
4736
RELEASE_NOTE: ${{ inputs.release_note }}
4837
run: |
4938
printf '%s\n' "$RELEASE_NOTE" > release-body.md
5039
5140
- name: Create or update GitHub release
52-
id: release
5341
env:
5442
GH_TOKEN: ${{ github.token }}
5543
RELEASE_ID: ${{ inputs.release_id }}
@@ -73,7 +61,7 @@ jobs:
7361
}
7462
7563
upsert_release() {
76-
local release_body release_payload response_file release_api_id release_url
64+
local release_body release_payload response_file release_api_id
7765
response_file="$(mktemp)"
7866
release_body="$(cat release-body.md)"
7967
release_payload="$(jq -nc \
@@ -103,200 +91,13 @@ jobs:
10391
--input - <<<"${release_payload}" >"${response_file}"
10492
fi
10593
106-
release_api_id="$(jq -r '.id' "${response_file}")"
10794
release_url="$(jq -r '.html_url // empty' "${response_file}")"
108-
if [ -z "${release_url}" ] || [ -z "${release_api_id}" ]; then
109-
echo "Missing release id/html_url in GitHub release response" >&2
95+
if [ -z "${release_url}" ]; then
96+
echo "Missing html_url in GitHub release response" >&2
11097
cat "${response_file}" >&2
11198
return 1
11299
fi
113-
114-
echo "release_api_id=${release_api_id}" >> "${GITHUB_OUTPUT}"
115-
echo "release_url=${release_url}" >> "${GITHUB_OUTPUT}"
116100
}
117101
118102
ensure_tag
119103
upsert_release
120-
121-
- name: Render media links and upload GIF previews
122-
env:
123-
GH_TOKEN: ${{ github.token }}
124-
RELEASE_ID: ${{ inputs.release_id }}
125-
RELEASE_API_ID: ${{ steps.release.outputs.release_api_id }}
126-
MEDIA_SOURCES: ${{ inputs.media_sources }}
127-
run: |
128-
node <<'NODE'
129-
const { execFileSync } = require('node:child_process');
130-
const crypto = require('node:crypto');
131-
const fs = require('node:fs/promises');
132-
const path = require('node:path');
133-
const os = require('node:os');
134-
135-
const repo = process.env.GITHUB_REPOSITORY;
136-
const token = process.env.GH_TOKEN;
137-
const releaseApiId = process.env.RELEASE_API_ID;
138-
const mediaSources = (() => {
139-
try { return JSON.parse(process.env.MEDIA_SOURCES || '{}') || {}; }
140-
catch { return {}; }
141-
})();
142-
143-
function getMediaType(url) {
144-
const cleanUrl = String(url).split(/[?#]/)[0].toLowerCase();
145-
if (/^https:\/\/github\.com\/user-attachments\/assets\//.test(cleanUrl)) return 'githubAttachment';
146-
if (/^https:\/\/github\.com\/[^/]+\/[^/]+\/assets\/[^/]+\/[^/?#]+/.test(cleanUrl)) return 'githubAttachment';
147-
if (/\.(png|jpe?g|gif|webp|avif|svg)$/.test(cleanUrl)) return 'image';
148-
if (/\.(mp4|webm|ogg|mov|m4v)$/.test(cleanUrl)) return 'video';
149-
return null;
150-
}
151-
152-
function safeAlt(label, fallback) {
153-
const value = String(label || fallback || 'Media').trim();
154-
return value.replace(/[\]\n\r]/g, '').replace(/^https?:\/\//i, '') || fallback || 'Media';
155-
}
156-
157-
function sanitizeAssetName(name, fallbackUrl) {
158-
const fallbackName = (() => {
159-
try {
160-
const pathname = new URL(fallbackUrl).pathname;
161-
return decodeURIComponent(pathname.split('/').filter(Boolean).pop() || 'video.mp4');
162-
} catch {
163-
return 'video.mp4';
164-
}
165-
})();
166-
const rawName = String(name || fallbackName || 'video.mp4').trim();
167-
const ext = path.extname(rawName);
168-
const base = path.basename(rawName, ext) || crypto.createHash('sha1').update(fallbackUrl).digest('hex').slice(0, 10);
169-
const safeBase = base
170-
.replace(/[\\/:*?"<>|#%{}]/g, '-')
171-
.replace(/\s+/g, '-')
172-
.replace(/-+/g, '-')
173-
.replace(/^-|-$/g, '') || 'video';
174-
return `${safeBase}-preview.gif`;
175-
}
176-
177-
function markdownImage(url, label) {
178-
return `![${safeAlt(label, 'Media')}](${url})`;
179-
}
180-
181-
async function githubJson(method, apiPath, body) {
182-
const options = {
183-
method,
184-
headers: {
185-
Accept: 'application/vnd.github+json',
186-
Authorization: `Bearer ${token}`,
187-
'X-GitHub-Api-Version': '2022-11-28',
188-
},
189-
};
190-
if (body !== undefined) {
191-
options.headers['Content-Type'] = 'application/json';
192-
options.body = JSON.stringify(body);
193-
}
194-
const response = await fetch(`https://api.github.com${apiPath}`, options);
195-
if (!response.ok) throw new Error(`${method} ${apiPath} failed: HTTP ${response.status}: ${await response.text()}`);
196-
if (response.status === 204) return null;
197-
return response.json();
198-
}
199-
200-
async function uploadGifAsset(filePath, assetName) {
201-
const assets = await githubJson('GET', `/repos/${repo}/releases/${releaseApiId}/assets?per_page=100`);
202-
const existing = assets.find((asset) => asset.name === assetName);
203-
if (existing) await githubJson('DELETE', `/repos/${repo}/releases/assets/${existing.id}`);
204-
205-
const buffer = await fs.readFile(filePath);
206-
const response = await fetch(`https://uploads.github.com/repos/${repo}/releases/${releaseApiId}/assets?name=${encodeURIComponent(assetName)}`, {
207-
method: 'POST',
208-
headers: {
209-
Accept: 'application/vnd.github+json',
210-
Authorization: `Bearer ${token}`,
211-
'Content-Type': 'image/gif',
212-
'Content-Length': String(buffer.byteLength),
213-
'X-GitHub-Api-Version': '2022-11-28',
214-
},
215-
body: buffer,
216-
});
217-
if (!response.ok) throw new Error(`Upload ${assetName} failed: HTTP ${response.status}: ${await response.text()}`);
218-
const asset = await response.json();
219-
return asset.browser_download_url;
220-
}
221-
222-
async function renderVideo(url, label) {
223-
const source = mediaSources[url] || { url, name: label };
224-
const downloadUrl = source.url || url;
225-
const assetName = sanitizeAssetName(source.name || label, url);
226-
const workDir = await fs.mkdtemp(path.join(os.tmpdir(), 'release-video-'));
227-
const inputPath = path.join(workDir, 'input-video');
228-
const outputPath = path.join(workDir, assetName);
229-
230-
const response = await fetch(downloadUrl);
231-
if (!response.ok) throw new Error(`Download video failed for ${url}: HTTP ${response.status}`);
232-
await fs.writeFile(inputPath, Buffer.from(await response.arrayBuffer()));
233-
234-
execFileSync('ffmpeg', [
235-
'-y',
236-
'-i', inputPath,
237-
'-t', '6',
238-
'-vf', 'fps=12,scale=1280:-1:flags=lanczos,format=rgb24,split[s0][s1];[s0]palettegen=max_colors=256:stats_mode=full[p];[s1][p]paletteuse=dither=sierra2_4a',
239-
outputPath,
240-
], { stdio: 'inherit' });
241-
242-
const gifUrl = await uploadGifAsset(outputPath, assetName);
243-
return `\n\n<img src="${gifUrl}" alt="video preview" width="720">\n\n`;
244-
}
245-
246-
async function renderUrl(url, label) {
247-
const type = getMediaType(url);
248-
if (type === 'image') return markdownImage(url, label);
249-
if (type === 'githubAttachment') return `\n\n${url}\n\n`;
250-
if (type === 'video') return renderVideo(url, label);
251-
return null;
252-
}
253-
254-
async function renderMarkdown(markdown) {
255-
let rendered = String(markdown || '');
256-
const markdownLinkPattern = /(!?)\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/g;
257-
const linkMatches = [...rendered.matchAll(markdownLinkPattern)];
258-
for (const match of linkMatches) {
259-
const [fullMatch, bang, label, url] = match;
260-
if (bang) continue;
261-
const replacement = await renderUrl(url, label);
262-
if (replacement) rendered = rendered.replace(fullMatch, replacement);
263-
}
264-
265-
const bareUrlPattern = /(^|[\s>])(https?:\/\/[^\s<>)]+)/g;
266-
const bareMatches = [...rendered.matchAll(bareUrlPattern)];
267-
for (const match of bareMatches) {
268-
const [fullMatch, prefix, url] = match;
269-
const offset = match.index;
270-
if (prefix === '(' || rendered[offset - 1] === '(' || rendered[offset - 1] === '"') continue;
271-
const beforeUrl = rendered.slice(0, offset + prefix.length);
272-
const lastOpenParen = beforeUrl.lastIndexOf('(');
273-
const lastCloseParen = beforeUrl.lastIndexOf(')');
274-
if (lastOpenParen > lastCloseParen && /\[[^\]]*\]$/.test(beforeUrl.slice(0, lastOpenParen))) continue;
275-
const replacement = await renderUrl(url);
276-
if (replacement) rendered = rendered.replace(fullMatch, `${prefix}${replacement}`);
277-
}
278-
279-
return rendered.replace(/\n{3,}/g, '\n\n').trim();
280-
}
281-
282-
(async () => {
283-
const raw = await fs.readFile('release-body.md', 'utf8');
284-
const rendered = await renderMarkdown(raw);
285-
await fs.writeFile('release-body.md', `${rendered}\n`);
286-
})().catch((error) => {
287-
console.error(error);
288-
process.exit(1);
289-
});
290-
NODE
291-
292-
- name: Update GitHub release body with media previews
293-
env:
294-
GH_TOKEN: ${{ github.token }}
295-
RELEASE_API_ID: ${{ steps.release.outputs.release_api_id }}
296-
run: |
297-
set -euo pipefail
298-
release_body="$(cat release-body.md)"
299-
jq -nc --arg body "${release_body}" '{ body: $body }' |
300-
gh api "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_API_ID}" \
301-
--method PATCH \
302-
--input -

.github/workflows/unit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222

2323
strategy:
2424
matrix:
25-
node-version: [22.18.0]
25+
node-version: [22.22.3]
2626

2727
steps:
2828
- uses: actions/checkout@v4

.github/workflows/v2-benchmark-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323

2424
strategy:
2525
matrix:
26-
node-version: [22.18.0]
26+
node-version: [22.22.3]
2727

2828
steps:
2929
- uses: actions/checkout@v4

.github/workflows/v2-core-tests.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ jobs:
3333
steps:
3434
- uses: actions/checkout@v4
3535

36-
- name: Use Node.js 22.18.0
36+
- name: Use Node.js 22.22.3
3737
uses: actions/setup-node@v4
3838
with:
39-
node-version: 22.18.0
39+
node-version: 22.22.3
4040

4141
- name: 📥 Monorepo install
4242
uses: ./.github/actions/pnpm-install
@@ -63,10 +63,10 @@ jobs:
6363
steps:
6464
- uses: actions/checkout@v4
6565

66-
- name: Use Node.js 22.18.0
66+
- name: Use Node.js 22.22.3
6767
uses: actions/setup-node@v4
6868
with:
69-
node-version: 22.18.0
69+
node-version: 22.22.3
7070

7171
- name: 📥 Monorepo install
7272
uses: ./.github/actions/pnpm-install
@@ -102,10 +102,10 @@ jobs:
102102
steps:
103103
- uses: actions/checkout@v4
104104

105-
- name: Use Node.js 22.18.0
105+
- name: Use Node.js 22.22.3
106106
uses: actions/setup-node@v4
107107
with:
108-
node-version: 22.18.0
108+
node-version: 22.22.3
109109

110110
- name: 📥 Download all coverage artifacts
111111
uses: actions/download-artifact@v4

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ auto-install-peers=true
44
lockfile=true
55
# force use npmjs.org registry
66
registry=https://registry.npmjs.org/
7-
use-node-version=22.18.0
7+
use-node-version=22.22.3
88
save-prefix=''

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ SCRATCH ?= /tmp
4242

4343
UNAME_S := $(shell uname -s)
4444

45-
# set param statement_cache_size=1 to avoid query error `ERROR: cached plan must not change result type` after alter column type (modify field type)
46-
POSTGRES_PRISMA_DATABASE_URL ?= postgresql://teable:teable\@127.0.0.1:5432/teable?schema=public\&statement_cache_size=1
45+
# Disable prepared statement caching to avoid `ERROR: cached plan must not change result type`
46+
# after field schema changes in tests.
47+
POSTGRES_PRISMA_DATABASE_URL ?= postgresql://teable:teable\@127.0.0.1:5432/teable?schema=public\&statement_cache_size=0
4748

4849
# If the first make argument is "start", "stop"...
4950
ifeq (docker.start,$(firstword $(MAKECMDGOALS)))
@@ -201,7 +202,7 @@ postgres.integration.test: docker.create.network
201202
$(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) run -p 25432:5432 -d -T --no-deps --rm --name $$TEST_PG_CONTAINER_NAME teable-postgres; \
202203
chmod +x scripts/wait-for; \
203204
scripts/wait-for 127.0.0.1:25432 --timeout=15 -- echo 'pg database started successfully' && \
204-
export PRISMA_DATABASE_URL=postgresql://teable:teable@127.0.0.1:25432/e2e_test_teable?schema=public\&statement_cache_size=1\&connection_limit=20 && \
205+
export PRISMA_DATABASE_URL=postgresql://teable:teable@127.0.0.1:25432/e2e_test_teable?schema=public\&statement_cache_size=0\&connection_limit=20 && \
205206
make postgres.mode && \
206207
pnpm -F "./packages/**" run build && \
207208
pnpm g:test-e2e-cover && \

0 commit comments

Comments
 (0)