diff --git a/gitlinq/docs/index.html b/gitlinq/docs/index.html index 3f9b478..04a9eb9 100644 --- a/gitlinq/docs/index.html +++ b/gitlinq/docs/index.html @@ -2,6 +2,13 @@ + + + GitLinq 설명서가 이동했습니다 @@ -20,6 +27,10 @@ + + +

DevsLab / GitLinq

GitLinq 설명서가 이동했습니다

설치부터 변경 조회·취소와 서버 업데이트까지, 초보자 설명서를 회사 홈페이지에서 확인하세요.

diff --git a/gitlinq/index.html b/gitlinq/index.html index 743d6a2..53937ea 100644 --- a/gitlinq/index.html +++ b/gitlinq/index.html @@ -2,6 +2,13 @@ + + + GitLinq 소개 페이지가 이동했습니다 @@ -20,6 +27,10 @@ + + +

DevsLab / GitLinq

GitLinq 소개 페이지가 이동했습니다

GitLinq 소개와 다운로드 안내는 DevsLab 회사 홈페이지에서 제공합니다.

diff --git a/index.html b/index.html index 536d62a..2e3584f 100644 --- a/index.html +++ b/index.html @@ -2,6 +2,13 @@ + + + 데브스랩(DevsLab) 오픈소스 — 라이브 데모 허브 + + +
@@ -541,6 +552,7 @@

devslab-examples

github.com/devslab.kr + 개인정보처리방침
@@ -596,6 +608,18 @@

devslab-examples

'pt-BR': 'Open source por DevsLab', fr: 'Open source par DevsLab', de: 'Open Source von DevsLab', es: 'Código abierto por DevsLab', ar: 'مصدر مفتوح من DevsLab' })[lang]; + // The company policy (devslab.kr/privacy) covers this hub too. Its + // label follows devslab.kr's footer; ?lang= carries the reader's + // language across (devslab.kr shows Korean, or English otherwise). + var privacy = document.getElementById('privacy-link'); + privacy.textContent = ({ + ko: '개인정보처리방침', en: 'Privacy Policy', ja: 'プライバシーポリシー', + 'zh-HK': '私隱政策', 'zh-TW': '隱私權政策', hi: 'गोपनीयता नीति', + vi: 'Chính sách quyền riêng tư', id: 'Kebijakan Privasi', th: 'นโยบายความเป็นส่วนตัว', + 'pt-BR': 'Política de Privacidade', fr: 'Politique de confidentialité', de: 'Datenschutzerklärung', + es: 'Política de privacidad', ar: 'سياسة الخصوصية' + })[lang]; + privacy.href = 'https://devslab.kr/privacy/?lang=' + encodeURIComponent(lang); setAll('[data-i18n="live"]', t.live); setAll('[data-i18n="docs"]', t.docs); for (var key in t.cards) { diff --git a/tests/gtm.test.mjs b/tests/gtm.test.mjs new file mode 100644 index 0000000..22f011c --- /dev/null +++ b/tests/gtm.test.mjs @@ -0,0 +1,86 @@ +import assert from 'node:assert/strict'; +import { readdirSync, readFileSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { runInNewContext } from 'node:vm'; + +const CONTAINER = 'GTM-5WGTTWSF'; +const root = fileURLToPath(new URL('..', import.meta.url)); +const SKIP = new Set(['.git', 'node_modules', '.worktrees', '.playwright-cli']); + +// Every HTML file Pages serves, found by walking the tree rather than listed by +// hand, so a page added later fails here until it carries the container too. +function htmlFiles(dir) { + return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + if (SKIP.has(entry.name)) return []; + const path = join(dir, entry.name); + if (entry.isDirectory()) return htmlFiles(path); + return entry.name.endsWith('.html') ? [path] : []; + }); +} + +const pages = htmlFiles(root); + +test('the walk finds the hub and its move pages', () => { + const found = pages.map((p) => relative(root, p).replaceAll('\\', '/')).sort(); + for (const expected of ['index.html', 'gitlinq/index.html', 'gitlinq/docs/index.html']) { + assert.ok(found.includes(expected), `${expected} was not found`); + } +}); + +for (const page of pages) { + const name = relative(root, page).replaceAll('\\', '/'); + + test(`${name} loads GTM once, first thing after `, () => { + const html = readFileSync(page, 'utf8'); + assert.equal(html.split('googletagmanager.com/gtm.js?id=').length - 1, 1, 'exactly one GTM loader'); + const head = html.slice(html.indexOf(''), html.indexOf('')); + const afterCharset = head.slice(head.indexOf('') + ''.length); + assert.ok(head.includes(''), 'the page declares its charset'); + // Only whitespace and Google's own marker comment may sit between them. + assert.match(afterCharset, /^\s*\s*