├── .achecker.yml ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .lighthouserc.json ├── .prettierignore ├── .prettierrc.js ├── .vscode ├── launch.json ├── qwik-city.code-snippets └── qwik.code-snippets ├── AUTHORS.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── adapters └── static │ └── vite.config.ts ├── azure-pipelines.yml ├── docs ├── azure-pipelines │ └── dockerfile ├── developer.md ├── issue-template.md └── pull_request_template.md ├── package.json ├── pnpm-lock.yaml ├── postcss.config.cjs ├── public ├── _headers ├── favicon.svg ├── images │ ├── fbshare.webp │ ├── logos │ │ ├── MODA-logo.svg │ │ ├── PDIS-logo.webp │ │ ├── department-of-informations-technology-taipei-city-government-logo.webp │ │ ├── foundation-for-public-code-logo.webp │ │ ├── gov-uk-logo.webp │ │ ├── matrix-element-logo.webp │ │ └── yivi-logo.webp │ └── projects │ │ ├── dengue │ │ ├── backend.webp │ │ └── frontend.webp │ │ ├── find-babycare │ │ ├── 01_daycare_map.webp │ │ ├── 02_application _process.webp │ │ ├── 03_application_dashboard.webp │ │ ├── 04_application_management.webp │ │ └── 05_enrollment_management.webp │ │ ├── moda-ghg-inventory-system-frontend │ │ └── screenshot.webp │ │ ├── moda-official-website-full │ │ ├── screenshot-1.webp │ │ ├── screenshot-2.webp │ │ └── screenshot-3.webp │ │ ├── odf-application-tools │ │ ├── screenshot-1.webp │ │ ├── screenshot-2.webp │ │ └── screenshot-3.webp │ │ ├── opendata-frontend │ │ ├── screenshot-1.webp │ │ └── screenshot-2.webp │ │ └── taipei-city-dashboard │ │ └── screenshot-1.webp ├── manifest.json └── robots.txt ├── publiccode-parser ├── README.md ├── lib │ ├── collectProjects.ts │ ├── collectUniqueValues.ts │ ├── createIndex.ts │ ├── customizeProjects.ts │ ├── extractFilterTags.ts │ ├── getYamlFiles.ts │ ├── loadYamlFile.ts │ └── normalizeUnit.ts ├── listValue.ts ├── main.ts ├── outputs │ └── .gitkeep ├── parser.ts ├── projects │ ├── civic-tech-experimental-field-dengue-backend.yml │ ├── civic-tech-experimental-field-dengue-frontend.yml │ ├── civic-tech-experimental-field-find-babycare-backend.yml │ ├── civic-tech-experimental-field-find-babycare-frontend.yml │ ├── code-gov-tw.yml │ ├── gov-uk-forms-admin.yml │ ├── gov-uk-forms-product-page.yml │ ├── gov-uk-notify.yml │ ├── italia-publiccode-yml-editor.yml │ ├── italia-publiccode-yml-standard.yml │ ├── matrix-client-matrix-react-sdk.yml │ ├── matrix-client-vector-im.element-web.yml │ ├── moda-ghg-inventory-system-frontend.yml │ ├── moda-official-website-full.yml │ ├── odf-application-tools.yml │ ├── opendata-frontend.yml │ ├── pdis-lighthouse.yml │ ├── pdis-web-jekyll.yml │ ├── publiccodenet.standard.yml │ ├── taipei-city-dashboard.yml │ ├── yivi-privacybydesign.irmamobile.yml │ └── yivi-privacybydesign.pbdf-schememanager.yml └── template.yml ├── publiccode.yml ├── src ├── components │ ├── breadcrumb │ │ └── index.tsx │ ├── button │ │ └── index.tsx │ ├── footer │ │ ├── contact.tsx │ │ ├── index.tsx │ │ └── site-map.tsx │ ├── header │ │ ├── index.tsx │ │ ├── logo.png │ │ ├── nav-about.tsx │ │ ├── nav-language.tsx │ │ ├── nav-link.tsx │ │ ├── nav-mobile-about.tsx │ │ └── nav-mobile-language.tsx │ ├── link │ │ ├── index.tsx │ │ └── useLocaleLink.tsx │ ├── promotion │ │ └── index.tsx │ ├── router-head │ │ └── router-head.tsx │ └── section │ │ └── index.tsx ├── contexts │ └── theme-context.ts ├── data │ └── .gitkeep ├── entry.dev.tsx ├── entry.preview.tsx ├── entry.ssr.tsx ├── global.css ├── i18n-utils.ts ├── locales │ ├── message.en.json │ └── message.zh-Hant.json ├── media │ ├── icons │ │ ├── arrow-left-icon.svg │ │ ├── arrow-right-icon.svg │ │ ├── arrow-top-right-on-square.svg │ │ ├── bars3-icon.svg │ │ ├── chevron-down-icon.svg │ │ ├── chevron-left-icon-light.svg │ │ ├── chevron-left-icon.svg │ │ ├── chevron-right-icon-light.svg │ │ ├── chevron-right-icon.svg │ │ ├── chevron-up-icon.svg │ │ ├── exclamation-circle.svg │ │ ├── funnel-icon.svg │ │ ├── question-mark.svg │ │ └── x-mark-icon.svg │ ├── images │ │ ├── about-feature-01.png │ │ ├── about-feature-02.png │ │ ├── about-feature-03.png │ │ ├── about-feature-04.png │ │ ├── about-global.svg │ │ ├── about-work-all.png │ │ ├── future-law.png │ │ ├── future-platform.png │ │ ├── future-talent.png │ │ ├── hero.png │ │ ├── index-eco-system.png │ │ ├── index-info-card-gov.png │ │ ├── index-info-card-people.png │ │ ├── index-what-is-public-code.png │ │ ├── participate-screenshot-1.png │ │ └── participate-screenshot-2.png │ └── logos │ │ ├── Democracy-network-logo.svg │ │ └── MODA-logo.svg ├── root.tsx ├── routes │ ├── 404.tsx │ ├── [locale] │ │ ├── 404.tsx │ │ ├── about │ │ │ └── index.tsx │ │ ├── future-plan │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── layout.tsx │ │ ├── participate │ │ │ └── index.tsx │ │ ├── projects │ │ │ ├── [repo] │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ └── submit │ │ │ └── index.tsx │ ├── about │ │ ├── accordion.tsx │ │ ├── accordions.tsx │ │ ├── features.tsx │ │ ├── index.tsx │ │ └── public-code-work.tsx │ ├── future-plan │ │ └── index.tsx │ ├── index.tsx │ ├── layout.tsx │ ├── participate │ │ ├── block.tsx │ │ └── index.tsx │ ├── projects │ │ ├── [repo] │ │ │ ├── carousel.tsx │ │ │ ├── index.tsx │ │ │ ├── list.tsx │ │ │ └── openapi.tsx │ │ ├── filter-checkbox.tsx │ │ ├── filter.tsx │ │ ├── index.tsx │ │ ├── label.tsx │ │ ├── mobile-filter-close.tsx │ │ ├── mobile-filter-open.tsx │ │ ├── page-nav.tsx │ │ ├── page-next-button.tsx │ │ ├── page-number-button.tsx │ │ ├── page-prev-button.tsx │ │ ├── repo-block.tsx │ │ └── repo-list.tsx │ ├── service-worker.ts │ └── submit │ │ ├── form.tsx │ │ ├── index.tsx │ │ ├── modular-forms.tsx │ │ ├── text-input.tsx │ │ └── textarea-input.tsx └── types │ └── Project.ts ├── tailwind.config.js ├── tsconfig.json └── vite.config.ts /.achecker.yml: -------------------------------------------------------------------------------- 1 | # optional - Specify the rule archive 2 | # Default: latest 3 | # Run `npx achecker archives` for a list of valid ruleArchive ids and policy ids. 4 | # If "latest", will use the latest rule release 5 | # If "versioned" (supported in 3.1.61+), will use latest rule release at 6 | # the time this version of the tool was released 7 | ruleArchive: latest 8 | 9 | # optional - Specify one or many policies to scan. 10 | # i.e. For one policy use policies: IBM_Accessibility 11 | # i.e. Multiple policies: IBM_Accessibility,WCAG_2_1 12 | # Run `npx achecker archives` for a list of valid ruleArchive ids and policy ids 13 | policies: 14 | - IBM_Accessibility 15 | 16 | # optional - Specify one or many violation levels on which to fail the test 17 | # i.e. If specified violation then the testcase will only fail if 18 | # a violation is found during the scan. 19 | # i.e. failLevels: violation 20 | # i.e. failLevels: violation,potential violation or refer to below as a list 21 | # Default: violation, potentialviolation 22 | failLevels: 23 | - violation 24 | - potentialviolation 25 | 26 | # optional - Specify one or many violation levels which should be reported 27 | # i.e. If specified violation then in the report it would only contain 28 | # results which are level of violation. 29 | # i.e. reportLevels: violation 30 | # Valid values: violation, potentialviolation, recommendation, potentialrecommendation, manual 31 | # Default: violation, potentialviolation 32 | reportLevels: 33 | - violation 34 | - potentialviolation 35 | - recommendation 36 | - potentialrecommendation 37 | - manual 38 | 39 | # Optional - In which formats should the results be output 40 | # Valid values: json, csv, xlsx, html, disable 41 | # Default: json 42 | outputFormat: 43 | - html 44 | 45 | # Optional - Specify labels that you would like associated to your scan 46 | # 47 | # i.e. 48 | # label: Firefox,master,V12,Linux 49 | # label: 50 | # - Firefox 51 | # - master 52 | # - V12 53 | # - Linux 54 | # Default: N/A 55 | label: 56 | - master 57 | 58 | # optional - Where the scan results should be saved. 59 | # Default: results 60 | outputFolder: accessibility-check-results 61 | 62 | # Optional - Should the timestamp be included in the filename of the reports? 63 | # Default: true 64 | outputFilenameTimestamp: true 65 | 66 | # optional - Where the baseline results should be loaded from 67 | # Default: baselines 68 | baselineFolder: test/baselines 69 | 70 | # optional - Where the tool can read/write cached files (ace-node.js / archive.json) 71 | # Default: `${os.tmpdir()}/accessibility-checker/` 72 | cacheFolder: /tmp/accessibility-checker 73 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/.DS_Store 3 | *. 4 | .vscode/settings.json 5 | .history 6 | .yarn 7 | bazel-* 8 | bazel-bin 9 | bazel-out 10 | bazel-qwik 11 | bazel-testlogs 12 | dist 13 | dist-dev 14 | lib 15 | lib-types 16 | etc 17 | external 18 | node_modules 19 | temp 20 | tsc-out 21 | tsdoc-metadata.json 22 | target 23 | output 24 | rollup.config.js 25 | build 26 | .cache 27 | .vscode 28 | .rollup.cache 29 | dist 30 | tsconfig.tsbuildinfo 31 | vite.config.ts 32 | *.spec.tsx 33 | *.spec.ts 34 | .netlify 35 | pnpm-lock.yaml 36 | package-lock.json 37 | yarn.lock 38 | server 39 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:qwik/recommended", 12 | ], 13 | parser: "@typescript-eslint/parser", 14 | parserOptions: { 15 | tsconfigRootDir: __dirname, 16 | project: ["./tsconfig.json"], 17 | ecmaVersion: 2021, 18 | sourceType: "module", 19 | ecmaFeatures: { 20 | jsx: true, 21 | }, 22 | }, 23 | plugins: ["@typescript-eslint"], 24 | rules: { 25 | "@typescript-eslint/no-explicit-any": "off", 26 | "@typescript-eslint/explicit-module-boundary-types": "off", 27 | "@typescript-eslint/no-inferrable-types": "off", 28 | "@typescript-eslint/no-non-null-assertion": "off", 29 | "@typescript-eslint/no-empty-interface": "off", 30 | "@typescript-eslint/no-namespace": "off", 31 | "@typescript-eslint/no-empty-function": "off", 32 | "@typescript-eslint/no-this-alias": "off", 33 | "@typescript-eslint/ban-types": "off", 34 | "@typescript-eslint/ban-ts-comment": "off", 35 | "prefer-spread": "off", 36 | "no-case-declarations": "off", 37 | "no-console": "off", 38 | "@typescript-eslint/no-unused-vars": ["error"], 39 | "@typescript-eslint/consistent-type-imports": "warn", 40 | "@typescript-eslint/no-unnecessary-condition": "warn", 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | /dist 3 | /lib 4 | /lib-types 5 | /server 6 | 7 | # Development 8 | node_modules 9 | *.local 10 | publiccode-parser/generator.js 11 | publiccode-parser/outputs/* 12 | publiccode-parser/local/* 13 | 14 | # Cache 15 | .cache 16 | .mf 17 | .rollup.cache 18 | tsconfig.tsbuildinfo 19 | 20 | # Logs 21 | logs 22 | *.log 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | pnpm-debug.log* 27 | lerna-debug.log* 28 | 29 | # Editor 30 | .vscode/* 31 | !.vscode/launch.json 32 | !.vscode/*.code-snippets 33 | 34 | .idea 35 | .DS_Store 36 | *.suo 37 | *.ntvs* 38 | *.njsproj 39 | *.sln 40 | *.sw? 41 | 42 | # Yarn 43 | .yarn/* 44 | !.yarn/releases 45 | 46 | # Output 47 | accessibility-check-results 48 | 49 | # SonarQube 50 | .scannerwork 51 | 52 | # Lighthouseci 53 | .lighthouseci 54 | 55 | # Local 56 | src/data/projects.json 57 | src/data/filters.json 58 | src/data/index.json -------------------------------------------------------------------------------- /.lighthouserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ci": { 3 | "collect": { 4 | "maxAutodiscoverUrls": 15 5 | }, 6 | "assert": { 7 | "assertions": { 8 | "categories:performance": ["error", { "minScore": 0.95 }], 9 | "categories:accessibility": ["error", { "minScore": 0.95 }], 10 | "categories:best-practices": ["error", { "minScore": 0.95 }], 11 | "categories:seo": ["error", { "minScore": 0.95 }] 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/.DS_Store 3 | *. 4 | .vscode/settings.json 5 | .history 6 | .yarn 7 | bazel-* 8 | bazel-bin 9 | bazel-out 10 | bazel-qwik 11 | bazel-testlogs 12 | dist 13 | dist-dev 14 | lib 15 | lib-types 16 | etc 17 | external 18 | node_modules 19 | temp 20 | tsc-out 21 | tsdoc-metadata.json 22 | target 23 | output 24 | rollup.config.js 25 | build 26 | .cache 27 | .vscode 28 | .rollup.cache 29 | tsconfig.tsbuildinfo 30 | vite.config.ts 31 | *.spec.tsx 32 | *.spec.ts 33 | .netlify 34 | pnpm-lock.yaml 35 | package-lock.json 36 | yarn.lock 37 | server 38 | .lighthouseci 39 | src/data -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: ["prettier-plugin-tailwindcss"], 3 | }; 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Chrome", 9 | "request": "launch", 10 | "type": "chrome", 11 | "url": "http://localhost:5173", 12 | "webRoot": "${workspaceFolder}" 13 | }, 14 | { 15 | "type": "node", 16 | "name": "dev.debug", 17 | "request": "launch", 18 | "skipFiles": ["/**"], 19 | "cwd": "${workspaceFolder}", 20 | "program": "${workspaceFolder}/node_modules/vite/bin/vite.js", 21 | "args": ["--mode", "ssr", "--force"] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/qwik-city.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "onRequest": { 3 | "scope": "javascriptreact,typescriptreact", 4 | "prefix": "qonRequest", 5 | "description": "onRequest function for a route index", 6 | "body": [ 7 | "export const onRequest: RequestHandler = (request) => {", 8 | " $0", 9 | "};" 10 | ] 11 | }, 12 | "loader$": { 13 | "scope": "javascriptreact,typescriptreact", 14 | "prefix": "qloader$", 15 | "description": "loader$()", 16 | "body": ["export const $1 = routeLoader$(() => {", " $0", "});"] 17 | }, 18 | "action$": { 19 | "scope": "javascriptreact,typescriptreact", 20 | "prefix": "qaction$", 21 | "description": "action$()", 22 | "body": ["export const $1 = routeAction$((data) => {", " $0", "});"] 23 | }, 24 | "Full Page": { 25 | "scope": "javascriptreact,typescriptreact", 26 | "prefix": "qpage", 27 | "description": "Simple page component", 28 | "body": [ 29 | "import { component$ } from '@builder.io/qwik';", 30 | "", 31 | "export default component$(() => {", 32 | " $0", 33 | "});" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/qwik.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Qwik component (simple)": { 3 | "scope": "javascriptreact,typescriptreact", 4 | "prefix": "qcomponent$", 5 | "description": "Simple Qwik component", 6 | "body": [ 7 | "export const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = component$(() => {", 8 | " return <${2:div}>$4", 9 | "});" 10 | ] 11 | }, 12 | "Qwik component (props)": { 13 | "scope": "typescriptreact", 14 | "prefix": "qcomponent$ + props", 15 | "description": "Qwik component w/ props", 16 | "body": [ 17 | "export interface ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}Props {", 18 | " $2", 19 | "}", 20 | "", 21 | "export const $1 = component$<$1Props>((props) => {", 22 | " const ${2:count} = useSignal(0);", 23 | " return (", 24 | " <${3:div} on${4:Click}$={(ev) => {$5}}>", 25 | " $6", 26 | " ", 27 | " );", 28 | "});" 29 | ] 30 | }, 31 | "Qwik signal": { 32 | "scope": "javascriptreact,typescriptreact", 33 | "prefix": "quseSignal", 34 | "description": "useSignal() declaration", 35 | "body": ["const ${1:foo} = useSignal($2);", "$0"] 36 | }, 37 | "Qwik store": { 38 | "scope": "javascriptreact,typescriptreact", 39 | "prefix": "quseStore", 40 | "description": "useStore() declaration", 41 | "body": ["const ${1:state} = useStore({", " $2", "});", "$0"] 42 | }, 43 | "$ hook": { 44 | "scope": "javascriptreact,typescriptreact", 45 | "prefix": "q$", 46 | "description": "$() function hook", 47 | "body": ["$(() => {", " $0", "});", ""] 48 | }, 49 | "useVisibleTask": { 50 | "scope": "javascriptreact,typescriptreact", 51 | "prefix": "quseVisibleTask", 52 | "description": "useVisibleTask$() function hook", 53 | "body": ["useVisibleTask$(({ track }) => {", " $0", "});", ""] 54 | }, 55 | "useTask": { 56 | "scope": "javascriptreact,typescriptreact", 57 | "prefix": "quseTask$", 58 | "description": "useTask$() function hook", 59 | "body": [ 60 | "useTask$(({ track }) => {", 61 | " track(() => $1);", 62 | " $0", 63 | "});", 64 | "" 65 | ] 66 | }, 67 | "useResource": { 68 | "scope": "javascriptreact,typescriptreact", 69 | "prefix": "quseResource$", 70 | "description": "useResource$() declaration", 71 | "body": [ 72 | "const $1 = useResource$(({ track, cleanup }) => {", 73 | " $0", 74 | "});", 75 | "" 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | - 數位發展部 4 | - 致遠體驗設計 5 | - 乘聚科技 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 如何貢獻 2 | 3 | 我們歡迎各種類型的貢獻,包括但不限於: 4 | 5 | - 程式碼修正 6 | - 文件更新 7 | - 新功能 8 | 9 | ## 貢獻指南 10 | 11 | - 再提交貢獻前,請先閱讀這份指南。 12 | - 請將您的貢獻集中在單一議題上。 13 | - 確保您的程式碼符合我們的風格指引。 14 | - 若您為開發者可參考[開發的相關資訊](/docs/developer.md) 15 | 16 | ## 貢獻流程 17 | 18 | 1. 請以 release 分支作為基礎進行開發,將您想要貢獻的東西增加上去 19 | 2. 開啟 pull request 時請設定以分支 release 為併入的目標 20 | 3. 想確認 pull request 的貢獻是否符合我們的風格與品質可執行下列指令或經由團隊成員評論觸發 CI 執行 21 | 22 | - 建置前 23 | - `pnpm fmt.check` 24 | - 建置後 25 | - `pnpm a11y.check` 26 | - `lhci autorun` 27 | 28 | 4. 團隊審核後完成即併入 release 並建置預覽版網站 29 | 5. 再由團隊確認分支 release 是否合併回分支 main 並正式上線 30 | 31 | ## 您找到了一個程式錯誤(Bug)嗎? 32 | 33 | - 如果您找到了一個資訊安全漏洞,請不要建立一個 issue, 而是先查閱我們的[安全性政策](/SECURITY.md)。 34 | - 您可以藉由搜索關鍵字來查詢,確定現有 issue 中是否有已經回報的內容。 35 | - 如果找不到相關的 issue,請開啟新的 issue。請務必包含標題、清晰的描述、盡可能的相關資訊,以及程式反範例或可以執行的測試方法,用來示範預期的行爲。 36 | - 如果可以請使用[Issue 範本](/docs/issue-template.md)來建立並將相關的內容填寫上去。 37 | 38 | ## 您修復了一個程式錯誤(Bug)嗎? 39 | 40 | - 建立一個 pull request 並附上已修復程式碼。 41 | - 確認PR清楚描述了問題和解決方案,如果可以也一起附上相關的 Issue 編號。 42 | - 如果可以請使用[pull request範本](/docs/pull_request_template.md)來建立並將相關的內容填寫上去 43 | 44 | ## 您打算新增新功能或更改現有功能嗎? 您想要協助更新文件嗎? 45 | 46 | - 您可以藉由搜索關鍵字來查詢,現有的 issue 與 pull request 是否已經有已經存在的內容。 47 | - 如果找不到相關的 issue 或 pull request,請建立一個新的 pull request 並附上相關內容 48 | - 如果是新增功能或更改現有功能,請描述理由及做法。 49 | - 如果是更新文件,請說明修改的想法。 50 | 51 | 謝謝您的貢獻! 52 | 53 | 若還有其他問題的話也歡迎與我們聯繫! 54 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ministry of Digital Affairs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # code-gov-tw 2 | 3 | ## 簡介 4 | 5 | 本專案為公共程式平臺(code.gov.tw)的原始碼 6 | 7 | 主旨為促進民間或政府單位需要了解或使用公共程式的人,方便查閱公共程式的相關資訊而建立的平臺。 8 | 9 | ## 安裝指南 10 | 11 | 需具備 NodeJS 版本 18 以上的環境,執行下列指令複製專案及安裝必須的套件。 12 | 13 | ```shell 14 | git clone https://github.com/moda-gov-tw/code-gov-tw.git # 複製本專案原始碼 15 | cd code-gov-tw 16 | # 需安裝 pnpm 17 | pnpm install # 從網路下載所需的套件 18 | ``` 19 | 20 | 初次下載完本專後,需要先建立專案一覽與篩選器所需要的資料(位於 publiccode-parser/projects) 21 | 22 | ```shell 23 | pnpm build.data 24 | ``` 25 | 26 | 接著使用下列指令開啟網站後,正常會開啟一個瀏覽器並連到[網站首頁](http://localhost:5173/)。沒有自動啟動的話也可以看指令顯示的網址自行連線。接著就可以開始瀏覽目前網站現有的內容 27 | 28 | ```shell 29 | pnpm start 30 | ``` 31 | 32 | ## 使用範例 33 | 34 | 瀏覽下列的頁面 35 | 36 | - `首頁`與從選單進入的`認識公共程式`及`未來規劃頁面`頁面,說明 `公共程式` 是什麼樣的東西? 如何運作以及未來會如何發展 37 | - `公共程式一覽`頁面,顯示現有收錄的公共程式專案並提供篩選功能 38 | - 支援國際化 (internationalization) 功能。從自動產生的字典檔快速新增翻譯 39 | - 支援從 publiccode.yml 標準管理公共程式一覽的專案清單 40 | 41 | ## 如何貢獻 42 | 43 | 如果您想要為這個專案貢獻,請查閱[CONTRIBUTING.md](/CONTRIBUTING.md)。 44 | 45 | ## 資訊安全問題 46 | 47 | 如果您發現本平台存在資訊安全問題,請查閱[SECURITY.md](/SECURITY.md)。 48 | 49 | ## 授權 50 | 51 | © [作者與貢獻者](/AUTHORS.md) 52 | 53 | 此專案程式碼採用 MIT 授權。詳細內容請查閱[LICENSE.md](/LICENSE.md)。 54 | 此專案文件採用 CC0 授權。 55 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 謝謝您助力讓`公共程式平臺`成為一個大家都能安心使用的平臺。 2 | 3 | # 安全性政策 4 | 5 | ## 報告資訊安全問題 6 | 7 | 如果您發現問題,請不要在公開場合公佈。相反地,請將詳細資訊透過電子郵件發送給我們的團隊。 8 | 9 | ### 如何報告 10 | 11 | 請將您發現的問題詳細描述,並從[這裡](https://www-mailbox.moda.gov.tw/)聯繫我們。我們會盡快回覆您。 12 | 13 | ## 安全更新過程 14 | 15 | 當我們接收到問題報告時,我們將遵循以下流程: 16 | 17 | 1. 確認問題,並評估影響程度。 18 | 2. 開發一個或多個修補程式碼來解決該問題。 19 | 3. 在適當的平臺上發布更新,並通知使用者。 20 | 21 | 我們致力於在發現資訊安全問題後的 90 天內發布修補程式碼。 22 | -------------------------------------------------------------------------------- /adapters/static/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { staticAdapter } from "@builder.io/qwik-city/adapters/static/vite"; 2 | import { extendConfig } from "@builder.io/qwik-city/vite"; 3 | import baseConfig from "../../vite.config"; 4 | 5 | export default extendConfig(baseConfig, () => { 6 | return { 7 | build: { 8 | ssr: true, 9 | rollupOptions: { 10 | input: ["@qwik-city-plan"], 11 | }, 12 | }, 13 | plugins: [ 14 | staticAdapter({ 15 | origin: "https://code.gov.tw", 16 | }), 17 | ], 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /docs/azure-pipelines/dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/javascript-node:18-bookworm 2 | 3 | # Set variable so puppeteer will not try to download chromium 4 | ENV PUPPETEER_SKIP_DOWNLOAD=true 5 | ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable 6 | 7 | # Install utilities 8 | RUN apt-get update --fix-missing && apt-get -y upgrade && apt-get install -y git wget gnupg && apt-get clean 9 | 10 | # Install latest chrome stable package. 11 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - 12 | RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' 13 | RUN apt-get update \ 14 | && apt-get install -y google-chrome-stable --no-install-recommends \ 15 | && apt-get clean 16 | 17 | # Install Lighthouse CI 18 | RUN npm install -g @lhci/cli@0.13.0 19 | 20 | # Install accessibility-checker 21 | RUN npm install -g accessibility-checker 22 | 23 | # Install puppeteer 24 | RUN npm install -g puppeteer 25 | -------------------------------------------------------------------------------- /docs/developer.md: -------------------------------------------------------------------------------- 1 | # 開發指南 2 | 3 | ## 專案結構 4 | 5 | 這個專案使用了 Qwik 與[QwikCity](https://qwik.builder.io/qwikcity/overview/)。QwikCity 是建立在 Qwik 之上的一套附加工具,用以簡化完整網站建置的流程,包括基於目錄的路由、版面配置等功能。 6 | 7 | 在您的專案中,您將看到以下的目錄結構: 8 | 9 | ``` 10 | ├── public/ 11 | │ └── ... 12 | └── src/ 13 | ├── components/ 14 | │ └── ... 15 | ├── data/ 16 | │ └── ... 17 | ├── locales/ 18 | │ └── ... 19 | ├── media/ 20 | │ └── ... 21 | ├── routes/ 22 | │ └── ... 23 | └── types/ 24 | └── ... 25 | ``` 26 | 27 | - `src/components`:共用元件的目錄。 28 | - `src/data`:儲存專案列表及篩選選項。 29 | - `src/locales`:儲存國際化(i18n)的本地字串。 30 | - `src/media`:儲存媒體資源,如圖示、圖片,用於Qwik圖片優化。 31 | - `src/routes`:提供基於目錄的路由,可以包含`layout.tsx`版面配置檔案的階層結構,以及作為頁面的`index.tsx`檔案。此外,`index.ts`檔案是端點。若想暸解更多路由設定請看[官方文件](https://qwik.builder.io/qwikcity/routing/overview/) 32 | - `src/types`:儲存共用的 TypeScript 型別定義。 33 | - `public`:任何靜態資源,如圖片,都可以放在public目錄中。想暸解更多請看 [Vite public directory](https://vitejs.dev/guide/assets.html#the-public-directory) 34 | 35 | ## 開發階段 36 | 37 | 開發模式使用[Vite的開發伺服器](https://vitejs.dev/)。`dev`指令會在開發時進行伺服器端渲染(SSR)輸出。 38 | 39 | > 注意:在開發模式中,Vite 可能會請求大量的`.js`檔案。這並不代表Qwik的生產環境構建。 40 | 41 | - 這階段可能會用到下列指令 42 | - `pnpm fmt` : 調整所有程式碼風格 43 | 44 | ## 預覽階段 45 | 46 | 預覽指令會建立客戶端模組的生產構建,並運行一個本地伺服器。預覽伺服器僅為在本地預覽生產構建提供便利,不應作為生產伺服器使用。 47 | 48 | ```shell 49 | pnpm preview # or `pnpm preview` 50 | ``` 51 | 52 | ## 生產環境 53 | 54 | 生產構建將通過運行客戶端和伺服器構建指令來生成客戶端和伺服器模組。構建指令將使用 Typescript 對原始碼進行類型檢查。 55 | 56 | ```shell 57 | pnpm build # or `pnpm build` 58 | ``` 59 | 60 | - 這階段會用到的指令,可用於建置後的檢查,同時也是 CI 中使用的檢查 61 | - `pnpm a11y.check` 62 | - `lhci autorun` 63 | 64 | ## 部署 65 | 66 | 由於本專案目前使用靜態網頁部署, 執行完 `pnpm build` 後產生的 `dist` 資料夾的內容就可以直接上傳部署 67 | 68 | ## 測試靜態網站 69 | 70 | ```shell 71 | npx http-server ./dist 72 | ``` 73 | 74 | ## 支援工具 75 | 76 | ### publiccode-parser 77 | 78 | 這個工具用於更新專案列表和篩選器選項。 79 | 80 | 1. 建立符合[publiccode.yml 標準](https://github.com/publiccodeyml/publiccode.yml)的檔案, 可複製[範本](../publiccode-parser/template.yml)使用 81 | 82 | 2. 將該 yml 檔案放至路徑 `publiccode-parser/projects` 中 83 | 84 | 3. 執行程式並更新網站資料 85 | 86 | ```sh 87 | pnpm build.data 88 | ``` 89 | 90 | ### 國際化 Internationalization (i18n) 91 | 92 | - 從原始碼中取出要翻譯的字串 93 | 94 | ```shell 95 | pnpm i18n-extract 96 | ``` 97 | 98 | - 執行指令後會建立 `./src/locales/message.zh-hant.json` 檔案,以該檔案的內容建立其他語系檔案中並更新內容 (例如建立一個新的檔案 message.en.json 並將 `locale` 的值更新為 `en` 並翻譯內容的字串) 99 | -------------------------------------------------------------------------------- /docs/issue-template.md: -------------------------------------------------------------------------------- 1 | # 問題報告 2 | 3 | ## 描述 4 | 5 | 請在這裡詳細描述您遇到的問題。 6 | 7 | ## 重現步驟 8 | 9 | 請列出重現問題的步驟: 10 | 11 | 1. 步驟一 12 | 2. 步驟二 13 | 3. 步驟三 14 | 15 | ## 期望的行為 16 | 17 | 請描述您期望發生什麼事情。 18 | 19 | ## 發生環境 20 | 21 | - 作業系統與版本:[例如 iOS 15] 22 | - 瀏覽器與版本 [例如 Chrome 122.0.6261.129] 23 | 24 | ## 截圖 25 | 26 | 如果可以,請提供問題的截圖。 27 | 28 | ## 其他資訊 29 | 30 | 在這裡補充其他的相關資訊 (例如:瀏覽器控制台中看到的訊息) 31 | -------------------------------------------------------------------------------- /docs/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | ## 描述 4 | 5 | 請包含對變更的總結以及相關的問題。也請附上相關的動機和背景。 6 | 7 | ## 變更類型 8 | 9 | 請刪除不相關的類型 10 | 11 | - [ ] 新功能 12 | - [ ] Bug 修復 13 | - [ ] 效能最佳化 14 | - [ ] 文件更新 15 | 16 | ## 如何測試 17 | 18 | 請描述如何測試您的更改。 19 | 20 | **測試環境**: 21 | 22 | - 作業系統: 23 | - 瀏覽器版本: 24 | - 使用軟體: 25 | 26 | ## 檢查清單 27 | 28 | - [ ] 我的程式碼遵循這個專案的風格指南 29 | - [ ] 我已對自己的程式碼進行了檢查 30 | - [ ] 我已對新增的程式碼進行了註解,特別是在難以理解的區域 31 | - [ ] 我已對文件進行了相應的變更 32 | - [ ] 我的變更未產生新的警告 33 | - [ ] 我已新增或執行測試來證明我的修正有效或我的功能可行 34 | - [ ] 我已檢查程式碼並糾正了任何拼寫錯誤 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-gov-tw", 3 | "description": "A website dedicated to promoting public code projects.", 4 | "engines": { 5 | "node": ">=15.0.0" 6 | }, 7 | "private": true, 8 | "trustedDependencies": [ 9 | "sharp" 10 | ], 11 | "type": "module", 12 | "scripts": { 13 | "build": "npm run build.data && qwik build && npm run remove-view-transition", 14 | "build.client": "vite build && npm run i18n-translate && npm run i18n-translate-zh-hant", 15 | "build.preview": "vite build --ssr src/entry.preview.tsx", 16 | "build.server": "vite build -c adapters/static/vite.config.ts", 17 | "build.types": "tsc --incremental --noEmit", 18 | "build.data": "npx tsx publiccode-parser/main.ts && cp -r publiccode-parser/outputs/* src/data/", 19 | "deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'", 20 | "dev": "vite --mode ssr", 21 | "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force", 22 | "fmt": "prettier --write .", 23 | "fmt.check": "prettier --check .", 24 | "i18n-extract": "npm run prei18n-extract && node_modules/.bin/localize-extract -l zh-Hant -s \"dist/build/*.js\" -f json -o src/locales/message.zh-Hant.json", 25 | "i18n-translate": "node_modules/.bin/localize-translate -s \"*.js\" -l en -t src/locales/message.en.json -o dist/build/en -r ./dist/build", 26 | "i18n-translate-zh-hant": "node_modules/.bin/localize-translate -s \"*.js\" -l zh-Hant -t src/locales/message.zh-Hant.json -o dist/build -r ./dist/build", 27 | "lint": "eslint \"src/**/*.ts*\"", 28 | "a11y.check": "achecker ./dist", 29 | "prei18n-extract": "vite build", 30 | "preview": "qwik build preview && vite preview --open", 31 | "start": "vite --open --mode ssr", 32 | "qwik": "qwik", 33 | "remove-view-transition": "find ./dist -type f -name \"*.html\" -exec sed -i -e 's/ -------------------------------------------------------------------------------- /public/images/fbshare.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/fbshare.webp -------------------------------------------------------------------------------- /public/images/logos/PDIS-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/logos/PDIS-logo.webp -------------------------------------------------------------------------------- /public/images/logos/department-of-informations-technology-taipei-city-government-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/logos/department-of-informations-technology-taipei-city-government-logo.webp -------------------------------------------------------------------------------- /public/images/logos/foundation-for-public-code-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/logos/foundation-for-public-code-logo.webp -------------------------------------------------------------------------------- /public/images/logos/gov-uk-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/logos/gov-uk-logo.webp -------------------------------------------------------------------------------- /public/images/logos/matrix-element-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/logos/matrix-element-logo.webp -------------------------------------------------------------------------------- /public/images/logos/yivi-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/logos/yivi-logo.webp -------------------------------------------------------------------------------- /public/images/projects/dengue/backend.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/dengue/backend.webp -------------------------------------------------------------------------------- /public/images/projects/dengue/frontend.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/dengue/frontend.webp -------------------------------------------------------------------------------- /public/images/projects/find-babycare/01_daycare_map.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/find-babycare/01_daycare_map.webp -------------------------------------------------------------------------------- /public/images/projects/find-babycare/02_application _process.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/find-babycare/02_application _process.webp -------------------------------------------------------------------------------- /public/images/projects/find-babycare/03_application_dashboard.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/find-babycare/03_application_dashboard.webp -------------------------------------------------------------------------------- /public/images/projects/find-babycare/04_application_management.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/find-babycare/04_application_management.webp -------------------------------------------------------------------------------- /public/images/projects/find-babycare/05_enrollment_management.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/find-babycare/05_enrollment_management.webp -------------------------------------------------------------------------------- /public/images/projects/moda-ghg-inventory-system-frontend/screenshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/moda-ghg-inventory-system-frontend/screenshot.webp -------------------------------------------------------------------------------- /public/images/projects/moda-official-website-full/screenshot-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/moda-official-website-full/screenshot-1.webp -------------------------------------------------------------------------------- /public/images/projects/moda-official-website-full/screenshot-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/moda-official-website-full/screenshot-2.webp -------------------------------------------------------------------------------- /public/images/projects/moda-official-website-full/screenshot-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/moda-official-website-full/screenshot-3.webp -------------------------------------------------------------------------------- /public/images/projects/odf-application-tools/screenshot-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/odf-application-tools/screenshot-1.webp -------------------------------------------------------------------------------- /public/images/projects/odf-application-tools/screenshot-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/odf-application-tools/screenshot-2.webp -------------------------------------------------------------------------------- /public/images/projects/odf-application-tools/screenshot-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/odf-application-tools/screenshot-3.webp -------------------------------------------------------------------------------- /public/images/projects/opendata-frontend/screenshot-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/opendata-frontend/screenshot-1.webp -------------------------------------------------------------------------------- /public/images/projects/opendata-frontend/screenshot-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/opendata-frontend/screenshot-2.webp -------------------------------------------------------------------------------- /public/images/projects/taipei-city-dashboard/screenshot-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/public/images/projects/taipei-city-dashboard/screenshot-1.webp -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/web-manifest-combined.json", 3 | "name": "Public Code Platform — 數位發展部 Ministry of Digital Affairs", 4 | "short_name": "Public Code Platform", 5 | "start_url": ".", 6 | "display": "standalone", 7 | "background_color": "#fff", 8 | "description": "數位發展部 Ministry of Digital Affairs (moda) 作為臺灣數位發展的「mòda(馬達)」,將連結「公民」與「技術」、提升「產業」及「安全」,實現智慧國家的願景,並以臺灣模式持續引領世界。" 9 | } 10 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /cdn-cgi/ -------------------------------------------------------------------------------- /publiccode-parser/README.md: -------------------------------------------------------------------------------- 1 | # publiccode-parser 2 | 3 | 此腳本用於解析指定目錄中符合publiccode.yml標準的 YAML 檔案,從中取出資料,然後將資料整理後的資料儲存為 JSON 檔案。 4 | 5 | 主要會存成下面三種檔案 6 | 7 | - filters.json 篩選器的設定,依據專案 publiccode.yml 的內容而定 8 | - projects.json 本地的專案資料 9 | - index.json: 預先產生篩選器的索引 10 | 11 | ## 開發 12 | 13 | 以下指南將幫助您在本地機器上準備環境並執行程式,用於開發和測試目的。 14 | 15 | ### 先決條件 16 | 17 | - 您的本地機器上安裝了 Node.js 版本 18 以上。 18 | - 在您的專案中或全域安裝了 TypeScript。您可以使用以下 npm 命令進行安裝:`npm install -g typescript` 19 | 20 | ### 安裝 21 | 22 | 1. 複製 code-gov-tw 專案到您的本地機器。 23 | 2. 進入到專案根目錄。 24 | 3. 運行 `npm install` 來安裝必要的套件。 25 | 26 | ### 開發 27 | 28 | 修改 `publiccode-parser/main.ts` 與 `publiccode-parser/lib/*.ts` 中的內容 29 | 30 | ### 使用方式 31 | 32 | 1. 將您專案的 publiccode.yml 檔案放置在 `publiccode-parser/projects` 目錄中。 33 | 2. 使用命令 `pnpm build.data` 運行腳本。 34 | 3. 腳本將解析 YAML 檔案,取得必要的資料,並將其儲存為 `./outputs` 目錄中的 JSON 檔案。 35 | 4. 將`outputs` 產生出來的檔案複製到網站的 `src/data/` 中 36 | 5. 網站在建置時就會使用到 37 | 38 | ```sh 39 | # 快速更新的指令 40 | pnpm build.data && cp -r publiccode-parser/outputs/* src/data/ 41 | ``` 42 | -------------------------------------------------------------------------------- /publiccode-parser/lib/collectProjects.ts: -------------------------------------------------------------------------------- 1 | import { loadYamlFile } from "./loadYamlFile"; 2 | import type { Project } from "~/types/Project"; 3 | 4 | export function collectProjects(filePaths: string[]): Project[] { 5 | const projects: Project[] = []; 6 | 7 | filePaths.forEach((filePath, index) => { 8 | try { 9 | const data = loadYamlFile(filePath); 10 | projects.push({ id: index, ...data }); 11 | } catch (e) { 12 | console.error( 13 | `Failed to load or parse YAML file: ${filePath}, Error: ${e}`, 14 | ); 15 | } 16 | }); 17 | 18 | return projects; 19 | } -------------------------------------------------------------------------------- /publiccode-parser/lib/collectUniqueValues.ts: -------------------------------------------------------------------------------- 1 | import { loadYamlFile } from "./loadYamlFile"; 2 | import { Dependency } from "../../src/types/Project"; 3 | 4 | // Function to collect unique values from specified keys in an array of YAML file paths 5 | export function collectUniqueValues(filePaths: string[]): { 6 | platforms: string[]; 7 | categories: string[]; 8 | mainCopyrightOwners: string[]; 9 | repoOwners: string[]; 10 | features: string[]; 11 | techStacks: string[]; 12 | } { 13 | const platformsSet = new Set(); 14 | const categoriesSet = new Set(); 15 | const mainCopyrightOwnerSet = new Set(); 16 | const repoOwnersSet = new Set(); 17 | const featuresSet = new Set(); 18 | const techStacksSet = new Set(); 19 | 20 | filePaths.forEach((filePath) => { 21 | try { 22 | const data = loadYamlFile(filePath); 23 | 24 | // Collect platforms 25 | if (data && Array.isArray(data.platforms)) { 26 | data.platforms.forEach((platform: string) => 27 | platformsSet.add(platform), 28 | ); 29 | } 30 | 31 | // Collect categories 32 | if (data && Array.isArray(data.categories)) { 33 | data.categories.forEach((category: string) => 34 | categoriesSet.add(category), 35 | ); 36 | } 37 | 38 | // Collect legal.mainCopyrightOwner 39 | if ( 40 | data?.legal?.mainCopyrightOwner && 41 | typeof data.legal.mainCopyrightOwner === "string" 42 | ) { 43 | mainCopyrightOwnerSet.add(data.legal.mainCopyrightOwner); 44 | } 45 | 46 | // Collect legal.repoOwner 47 | if (data?.legal?.repoOwner && typeof data.legal.repoOwner === "string") { 48 | const firstPart = data.legal.repoOwner.split(" ")[0]; 49 | repoOwnersSet.add(firstPart); 50 | } 51 | 52 | // Collect description."zh-Hant".features 53 | if ( 54 | data?.description?.["zh-Hant"]?.features && 55 | Array.isArray(data.description["zh-Hant"].features) 56 | ) { 57 | data.description["zh-Hant"].features.forEach((feature: string) => 58 | featuresSet.add(feature), 59 | ); 60 | } 61 | 62 | // Collect dependsOn.open.name (system dependencies) 63 | if (data?.dependsOn?.open) { 64 | data.dependsOn.open.forEach((tech: Dependency) => 65 | techStacksSet.add(tech.name), 66 | ); 67 | } 68 | 69 | // Collect tw.techStacks (detail dependencies) 70 | if (data?.tw?.techStacks) { 71 | data.tw.techStacks.forEach((tech: Dependency) => 72 | techStacksSet.add(tech.name), 73 | ); 74 | } 75 | } catch (e) { 76 | console.error( 77 | `Failed to load or parse YAML file: ${filePath}, Error: ${e}`, 78 | ); 79 | } 80 | }); 81 | 82 | // Convert the Sets to arrays to return the unique values 83 | return { 84 | platforms: Array.from(platformsSet), 85 | categories: Array.from(categoriesSet), 86 | mainCopyrightOwners: Array.from(mainCopyrightOwnerSet), 87 | repoOwners: Array.from(repoOwnersSet), 88 | features: Array.from(featuresSet), 89 | techStacks: Array.from(techStacksSet), 90 | }; 91 | } -------------------------------------------------------------------------------- /publiccode-parser/lib/createIndex.ts: -------------------------------------------------------------------------------- 1 | import { Project } from '../../src/types/Project'; 2 | import { normalizeUnit } from './normalizeUnit'; 3 | 4 | type Index = { 5 | features: Record; 6 | repoOwners: Record; 7 | techStacks: Record; 8 | }; 9 | 10 | // Define the main providers, other will be grouped under "其他" 11 | const mainProviders = [ 12 | "數位發展部", 13 | "行政院公共數位創新空間(PDIS)", 14 | "臺北市政府資訊局", 15 | ]; 16 | 17 | export function createIndex(projects : Project[]): Index { 18 | const index: Index = { 19 | features: {}, 20 | repoOwners: {"其他": []}, 21 | techStacks: {}, 22 | }; 23 | 24 | projects.forEach((project) => { 25 | Object.values(project.description).forEach((desc) => { 26 | desc.features.forEach((name) => { 27 | if (!index.features[name]) { 28 | index.features[name] = []; 29 | } 30 | index.features[name].push(project.id); 31 | }); 32 | }); 33 | 34 | const { mainCopyrightOwner } = project.legal; 35 | const normalizeOwner = normalizeUnit(mainCopyrightOwner) 36 | 37 | if (mainProviders.includes(normalizeOwner)) { 38 | if (!index.repoOwners[normalizeOwner]) { 39 | index.repoOwners[normalizeOwner] = []; 40 | } 41 | index.repoOwners[normalizeOwner].push(project.id) 42 | } else { 43 | index.repoOwners["其他"].push(project.id) 44 | } 45 | 46 | if (project.dependsOn) { 47 | [ 48 | ...(project.dependsOn.open || []), 49 | ...(project.dependsOn.close || []), 50 | ].forEach((dep) => { 51 | if (!index.techStacks[dep.name]) { 52 | index.techStacks[dep.name] = []; 53 | } 54 | index.techStacks[dep.name].push(project.id); 55 | }); 56 | } 57 | 58 | if (project.tw.techStacks) { 59 | project.tw.techStacks.forEach((techStack) => { 60 | if (!index.techStacks[techStack.name]) { 61 | index.techStacks[techStack.name] = []; 62 | } 63 | index.techStacks[techStack.name].push(project.id); 64 | }); 65 | } 66 | }); 67 | 68 | return index; 69 | } -------------------------------------------------------------------------------- /publiccode-parser/lib/customizeProjects.ts: -------------------------------------------------------------------------------- 1 | import type { Project } from "../../src/types/Project"; 2 | import { normalizeUnit } from "./normalizeUnit"; 3 | 4 | // Define the logos for the main copyright owners 5 | const mainCopyrightOwnerLogos: Record = { 6 | "數位發展部": "/images/logos/MODA-logo.svg", 7 | "英國政府數位服務團 Government Digital Service": "/images/logos/gov-uk-logo.webp", 8 | "Foundation for Public Code": "/images/logos/foundation-for-public-code-logo.webp", 9 | "Yivi": "/images/logos/yivi-logo.webp", 10 | "臺北市政府資訊局": "/images/logos/department-of-informations-technology-taipei-city-government-logo.webp", 11 | "行政院公共數位創新空間(PDIS)": "/images/logos/PDIS-logo.webp", 12 | "Element": "/images/logos/matrix-element-logo.webp", 13 | }; 14 | 15 | export function customizeProjects(projects: Project[]) { 16 | const modifiedProjects: Project[] = []; 17 | 18 | for (const project of projects) { 19 | const newProject: Project = { ...project }; 20 | 21 | const { mainCopyrightOwner } = project.legal; 22 | 23 | if (!mainCopyrightOwner) continue; 24 | 25 | const normalizeOwner = normalizeUnit(mainCopyrightOwner); 26 | // customize provider logo 27 | newProject.tw = { 28 | ...newProject.tw, 29 | mainCopyrightOwnerLogo: mainCopyrightOwnerLogos[normalizeOwner], 30 | }; 31 | modifiedProjects.push(newProject); 32 | } 33 | 34 | return modifiedProjects; 35 | } 36 | -------------------------------------------------------------------------------- /publiccode-parser/lib/extractFilterTags.ts: -------------------------------------------------------------------------------- 1 | import { Project, Dependency } from "../../src/types/Project"; 2 | 3 | export function extractFilterTags(data: Project) { 4 | const featuresSet = new Set(); 5 | const techStacksSet = new Set(); 6 | const repoOwnersSet = new Set(); 7 | const mainCopyrightOwnerSet = new Set(); 8 | 9 | // Collect description."zh-Hant".features 10 | if (Array.isArray(data.description["zh-Hant"].features)) { 11 | data.description["zh-Hant"].features.forEach((feature: string) => 12 | featuresSet.add(feature), 13 | ); 14 | } 15 | 16 | // Collect dependsOn.open.name (system dependencies) 17 | if (data.dependsOn?.open) { 18 | data.dependsOn.open.forEach((tech: Dependency) => 19 | techStacksSet.add(tech.name), 20 | ); 21 | } 22 | 23 | // Collect tw.techStacks (detail dependencies) 24 | if (Array.isArray(data.tw.techStacks)) { 25 | data.tw.techStacks.forEach((tech: Dependency) => 26 | techStacksSet.add(tech.name), 27 | ); 28 | } 29 | 30 | // Collect legal.repoOwner 31 | if (data.legal.repoOwner) { 32 | const firstPart = data.legal.repoOwner.split(" ")[0]; 33 | repoOwnersSet.add(firstPart); 34 | } 35 | 36 | // Collect legal.mainCopyrightOwner 37 | if (data.legal.mainCopyrightOwner) { 38 | const firstPart = data.legal.mainCopyrightOwner.split(" ")[0]; 39 | mainCopyrightOwnerSet.add(firstPart); 40 | } 41 | 42 | return { 43 | features: Array.from(featuresSet), 44 | techStacks: Array.from(techStacksSet), 45 | repoOwners: Array.from(repoOwnersSet), 46 | mainCopyrightOwners: Array.from(mainCopyrightOwnerSet), 47 | }; 48 | } -------------------------------------------------------------------------------- /publiccode-parser/lib/getYamlFiles.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from "fs"; 2 | import { join } from "path"; 3 | 4 | // Function to get YAML files in a directory 5 | export function getYamlFiles(directoryPath: string): string[] { 6 | try { 7 | const files = readdirSync(directoryPath); 8 | // Filter out files that end with .yaml or .yml 9 | const yamlFiles = files.filter( 10 | (file) => file.endsWith(".yaml") || file.endsWith(".yml"), 11 | ); 12 | // Return the full path of the files 13 | return yamlFiles.map((file) => join(directoryPath, file)); 14 | } catch (e) { 15 | console.error(`Failed to read directory: ${e}`); 16 | return []; 17 | } 18 | } -------------------------------------------------------------------------------- /publiccode-parser/lib/loadYamlFile.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, readdirSync, writeFileSync } from "fs"; 2 | import { parse } from "yaml"; 3 | import { extractFilterTags } from "./extractFilterTags"; 4 | 5 | // Function to load and parse a YAML file 6 | export function loadYamlFile(filePath: string): any { 7 | try { 8 | // Read the file 9 | const fileContents = readFileSync(filePath, "utf8"); 10 | // Parse the YAML content 11 | const data = parse(fileContents); 12 | const filterTags = extractFilterTags(data); 13 | return { ...data, filterTags }; 14 | } catch (e) { 15 | console.error(`Failed to load or parse YAML file: ${e}`); 16 | return null; 17 | } 18 | } -------------------------------------------------------------------------------- /publiccode-parser/lib/normalizeUnit.ts: -------------------------------------------------------------------------------- 1 | // Normalize the unit name like the following: 2 | // "數位發展部" => "數位發展部" 3 | // "數位發展部 資訊處" => "數位發展部" 4 | // "數位發展部 數位政府司" => "數位發展部" 5 | export function normalizeUnit(unit: string): string { 6 | if (unit.startsWith("數位發展部")) { 7 | return "數位發展部"; 8 | } 9 | return unit; 10 | } -------------------------------------------------------------------------------- /publiccode-parser/listValue.ts: -------------------------------------------------------------------------------- 1 | export const platforms: string[] = [ 2 | "web", 3 | "windows", 4 | "mac", 5 | "linux", 6 | "ios", 7 | "android", 8 | ]; 9 | export const categories: string[] = [ 10 | "accounting", 11 | "agile-project-management", 12 | "applicant-tracking", 13 | "application-development", 14 | "appointment-scheduling", 15 | "backup", 16 | "billing-and-invoicing", 17 | "blog", 18 | "budgeting", 19 | "business-intelligence", 20 | "business-process-management", 21 | "cad", 22 | "call-center-management", 23 | "cloud-management", 24 | "collaboration", 25 | "communications", 26 | "compliance-management", 27 | "contact-management", 28 | "content-management", 29 | "crm", 30 | "customer-service-and-support", 31 | "data-analytics", 32 | "data-collection", 33 | "data-visualization", 34 | "digital-asset-management", 35 | "digital-citizenship", 36 | "document-management", 37 | "donor-management", 38 | "e-commerce", 39 | "e-signature", 40 | "educational-content", 41 | "email-management", 42 | "email-marketing", 43 | "employee-management", 44 | "enterprise-project-management", 45 | "enterprise-social-networking", 46 | "erp", 47 | "event-management", 48 | "facility-management", 49 | "feedback-and-reviews-management", 50 | "financial-reporting", 51 | "fleet-management", 52 | "fundraising", 53 | "gamification", 54 | "geographic-information-systems", 55 | "grant-management", 56 | "graphic-design", 57 | "help-desk", 58 | "hr", 59 | "ide", 60 | "identity-management", 61 | "instant-messaging", 62 | "inventory-management", 63 | "it-asset-management", 64 | "it-development", 65 | "it-management", 66 | "it-security", 67 | "it-service-management", 68 | "knowledge-management", 69 | "learning-management-system", 70 | "marketing", 71 | "mind-mapping", 72 | "mobile-marketing", 73 | "mobile-payment", 74 | "network-management", 75 | "office", 76 | "online-booking", 77 | "online-community", 78 | "payment-gateway", 79 | "payroll", 80 | "predictive-analysis", 81 | "procurement", 82 | "productivity-suite", 83 | "project-collaboration", 84 | "project-management", 85 | "property-management", 86 | "real-estate-management", 87 | "remote-support", 88 | "resource-management", 89 | "sales-management", 90 | "seo", 91 | "service-desk", 92 | "social-media-management", 93 | "survey", 94 | "talent-management", 95 | "task-management", 96 | "taxes-management", 97 | "test-management", 98 | "time-management", 99 | "time-tracking", 100 | "translation", 101 | "video-conferencing", 102 | "video-editing", 103 | "visitor-management", 104 | "voip", 105 | "warehouse-management", 106 | "web-collaboration", 107 | "web-conferencing", 108 | "website-builder", 109 | "whistleblowing", 110 | "workflow-management", 111 | ]; 112 | export const developmentStatus: string[] = [ 113 | "concept", 114 | "development", 115 | "beta", 116 | "stable", 117 | "obsolete", 118 | ]; 119 | export const maintenanceTypes: string[] = [ 120 | "internal", 121 | "contract", 122 | "community", 123 | "none", 124 | ]; 125 | export const softwareType: string[] = [ 126 | "standalone/mobile", 127 | "standalone/iot", 128 | "standalone/desktop", 129 | "standalone/web", 130 | "standalone/backend", 131 | "standalone/other", 132 | "addon", 133 | "library", 134 | "configurationFiles", 135 | ]; 136 | export const intendedAutienceScopeList: string[] = [ 137 | "agriculture", 138 | "culture", 139 | "defence", 140 | "education", 141 | "emergency-services", 142 | "employment", 143 | "energy", 144 | "environment", 145 | "finance-and-economic-development", 146 | "foreign-affairs", 147 | "government", 148 | "healthcare", 149 | "infrastructures", 150 | "justice", 151 | "local-authorities", 152 | "manufacturing", 153 | "research", 154 | "science-and-technology", 155 | "security", 156 | "society", 157 | "sport", 158 | "tourism", 159 | "transportation", 160 | "welfare", 161 | ]; 162 | -------------------------------------------------------------------------------- /publiccode-parser/main.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from "fs"; 2 | import { join } from "path"; 3 | import { collectProjects } from "./lib/collectProjects"; 4 | import { collectUniqueValues } from "./lib/collectUniqueValues"; 5 | import { getYamlFiles } from "./lib/getYamlFiles"; // Provide the correct file path for the module 6 | import { customizeProjects } from "./lib/customizeProjects"; 7 | import { createIndex } from "./lib/createIndex"; 8 | 9 | // get current directory 10 | const baseDir = new URL(".", import.meta.url).pathname; 11 | 12 | const directoryPath = join(baseDir, "./projects"); // Replace with your directory path 13 | const yamlFiles = getYamlFiles(directoryPath); 14 | const filterValues = collectUniqueValues(yamlFiles); 15 | 16 | const finalFilters = { 17 | platforms: [...filterValues.platforms], 18 | categories: [...filterValues.categories], 19 | mainCopyrightOwners: [...filterValues.mainCopyrightOwners], 20 | repoOwners: [...filterValues.repoOwners, "其他"], 21 | features: [...filterValues.features], 22 | techStacks: [...filterValues.techStacks], 23 | }; 24 | const projects = collectProjects(yamlFiles); 25 | const modifiedProjects = customizeProjects(projects); 26 | const index = createIndex(modifiedProjects); 27 | 28 | const outputFiltersFile = join(baseDir, "./outputs/filters.json"); 29 | const outputProjectsFile = join(baseDir, "./outputs/projects.json"); 30 | const outputIndexFile = join(baseDir, "./outputs/index.json"); 31 | 32 | try { 33 | writeFileSync(outputProjectsFile, JSON.stringify(modifiedProjects, null, 2)); 34 | writeFileSync(outputFiltersFile, JSON.stringify(finalFilters, null, 2)); 35 | writeFileSync(outputIndexFile, JSON.stringify(index, null, 2)); 36 | console.log( 37 | `Successfully saved unique filter values to ${outputFiltersFile}`, 38 | ); 39 | console.log(`Successfully saved projects to ${outputProjectsFile}`); 40 | } catch (e) { 41 | console.error(`Failed to save unique values to JSON file: ${e}`); 42 | } 43 | -------------------------------------------------------------------------------- /publiccode-parser/outputs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/publiccode-parser/outputs/.gitkeep -------------------------------------------------------------------------------- /publiccode-parser/parser.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/publiccode-parser/parser.ts -------------------------------------------------------------------------------- /publiccode-parser/projects/civic-tech-experimental-field-dengue-backend.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: civic-tech-experimental-field-dengue-backend 3 | url: "https://github.com/moda-gov-tw/civic-tech-experimental-field-dengue-backend" 4 | releaseDate: "2023-12-14" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - data-visualization 10 | - digital-citizenship 11 | - collaboration 12 | - communications 13 | developmentStatus: stable 14 | softwareType: standalone/web 15 | description: 16 | zh-Hant: 17 | localisedName: 臺南市登革熱防疫現場數位工具 - 後端 18 | shortDescription: > 19 | 本案為「112年數位發展部公民科技試驗場域示範案」由「好想打流感」團隊開發的「臺南市登革熱防疫現場數位工具」透過與臺南市政府衛生局的合作,協助開發數位工具以簡化登革熱防治工作上的流程,並改善目前繁瑣的人工確認作業及掌握更即時的資訊。 20 | features: 21 | - 表單填寫 / 資料上傳 22 | - 個案管理 23 | - 地圖資訊呈現 24 | - 資料一覽 / 檢索 25 | - 數據呈現 26 | screenshots: 27 | - /images/projects/dengue/backend.webp 28 | legal: 29 | license: MIT License 30 | mainCopyrightOwner: 數位發展部 31 | repoOwner: 數位發展部 32 | maintenance: 33 | type: internal 34 | contacts: 35 | - name: 劉澄真 36 | email: "rosalind.liu@moda.gov.tw" 37 | phone: "02-23800325" 38 | localisation: 39 | localisationReady: false 40 | availableLanguages: 41 | - zh-Hant 42 | dependsOn: 43 | open: 44 | - name: Node.js 45 | versionMin: "18" 46 | - name: "Firebase Realtime Database" 47 | usedBy: 48 | - "臺南市政府衛生局內部用於登革熱防疫作業" 49 | tw: 50 | countryExtensionVersion: "1.0.0" 51 | techStacks: 52 | - name: "Vue.js" 53 | - name: "HTML/CSS" 54 | - name: "JavaScript" 55 | vulnerabilityScanners: 56 | - "OWASP" 57 | # GitHub API 有資訊可以取得, 暫時手動填寫 58 | createdDate: "2023-11-22" 59 | -------------------------------------------------------------------------------- /publiccode-parser/projects/civic-tech-experimental-field-dengue-frontend.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: civic-tech-experimental-field-dengue-frontend 3 | url: "https://github.com/moda-gov-tw/civic-tech-experimental-field-dengue-frontend" 4 | releaseDate: "2023-12-14" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - data-visualization 10 | - digital-citizenship 11 | - collaboration 12 | - communications 13 | developmentStatus: stable 14 | softwareType: standalone/web 15 | description: 16 | zh-Hant: 17 | localisedName: 臺南市登革熱防疫現場數位工具 - 前臺 18 | shortDescription: > 19 | 本案為「112年數位發展部公民科技試驗場域示範案」由「好想打流感」團隊開發的「臺南市登革熱防疫現場數位工具」透過與臺南市政府衛生局的合作,協助開發數位工具以簡化登革熱防治工作上的流程,並改善目前繁瑣的人工確認作業及掌握更即時的資訊。 20 | features: 21 | - 表單填寫 / 資料上傳 22 | - 個案管理 23 | - 地圖資訊呈現 24 | - 資料一覽 / 檢索 25 | - 數據呈現 26 | screenshots: 27 | - /images/projects/dengue/frontend.webp 28 | legal: 29 | license: MIT License 30 | mainCopyrightOwner: 數位發展部 31 | repoOwner: 數位發展部 32 | maintenance: 33 | type: internal 34 | contacts: 35 | - name: 劉澄真 36 | email: "rosalind.liu@moda.gov.tw" 37 | phone: "02-23800325" 38 | localisation: 39 | localisationReady: false 40 | availableLanguages: 41 | - zh-Hant 42 | dependsOn: 43 | open: 44 | - name: Node.js 45 | versionMin: "18" 46 | - name: "MongoDB" 47 | versionMin: "4.4" 48 | usedBy: 49 | - "臺南市政府衛生局內部用於登革熱防疫作業" 50 | tw: 51 | countryExtensionVersion: "1.0.0" 52 | techStacks: 53 | - name: "Vue.js" 54 | - name: "HTML/CSS" 55 | - name: "JavaScript" 56 | vulnerabilityScanners: 57 | - "OWASP" 58 | # GitHub API 有資訊可以取得, 暫時手動填寫 59 | createdDate: "2023-11-22" 60 | -------------------------------------------------------------------------------- /publiccode-parser/projects/civic-tech-experimental-field-find-babycare-backend.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: civic-tech-experimental-field-find-babycare-backend 3 | url: "https://github.com/moda-gov-tw/civic-tech-experimental-field-find-babycare-backend" 4 | landingURL: "https://findbabycare.com/" 5 | releaseDate: "2023-12-14" 6 | platforms: 7 | - web 8 | categories: 9 | - data-collection 10 | - data-visualization 11 | - digital-citizenship 12 | - collaboration 13 | - communications 14 | developmentStatus: beta 15 | softwareType: standalone/web 16 | description: 17 | zh-Hant: 18 | localisedName: 公托報名管理系統(後端) 19 | shortDescription: > 20 | 本案為「112年數位發展部公民科技試驗場域示範案」由「一起加油陣線」團隊開發的「公托申請數位流程優化」透過與臺中市政府社會局的合作,提供既有公托申請流程解方,開發一申請公托的整合平臺,提供申請、查詢、審核及管理等功能。本案為示範案例尚未正式上線提供服務。 21 | features: 22 | - 表單填寫 / 資料上傳 23 | - 個案管理 24 | - 地圖資訊呈現 25 | screenshots: 26 | - /images/projects/find-babycare/01_daycare_map.webp 27 | - /images/projects/find-babycare/02_application _process.webp 28 | - /images/projects/find-babycare/03_application_dashboard.webp 29 | - /images/projects/find-babycare/04_application_management.webp 30 | - /images/projects/find-babycare/05_enrollment_management.webp 31 | legal: 32 | license: MIT License 33 | mainCopyrightOwner: 數位發展部 34 | repoOwner: 數位發展部 35 | maintenance: 36 | type: internal 37 | contacts: 38 | - name: 劉澄真 39 | email: "rosalind.liu@moda.gov.tw" 40 | phone: "02-23800325" 41 | localisation: 42 | localisationReady: false 43 | availableLanguages: 44 | - zh-Hant 45 | dependsOn: 46 | open: 47 | - name: PHP 48 | - name: MySQL 49 | tw: 50 | countryExtensionVersion: "1.0.0" 51 | techStacks: 52 | - name: "Vue.js" 53 | - name: "HTML/CSS" 54 | - name: "JavaScript" 55 | - name: "Laravel" 56 | vulnerabilityScanners: 57 | - "OWASP" 58 | # GitHub API 有資訊可以取得, 暫時手動填寫 59 | createdDate: "2023-11-22" 60 | -------------------------------------------------------------------------------- /publiccode-parser/projects/civic-tech-experimental-field-find-babycare-frontend.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: civic-tech-experimental-field-find-babycare-frontend 3 | url: "https://github.com/moda-gov-tw/civic-tech-experimental-field-find-babycare-frontend" 4 | landingURL: "https://findbabycare.com/" 5 | releaseDate: "2023-12-14" 6 | platforms: 7 | - web 8 | categories: 9 | - data-collection 10 | - data-visualization 11 | - digital-citizenship 12 | - collaboration 13 | - communications 14 | developmentStatus: beta 15 | softwareType: standalone/web 16 | description: 17 | zh-Hant: 18 | localisedName: 公托報名管理系統(前端) 19 | shortDescription: > 20 | 本案為「112年數位發展部公民科技試驗場域示範案」由「一起加油陣線」團隊開發的「公托申請數位流程優化」透過與臺中市政府社會局的合作,提供既有公托申請流程解方,開發一申請公托的整合平臺,提供申請、查詢、審核及管理等功能。本案為示範案例尚未正式上線提供服務。 21 | features: 22 | - 表單填寫 / 資料上傳 23 | - 個案管理 24 | - 地圖資訊呈現 25 | screenshots: 26 | - /images/projects/find-babycare/01_daycare_map.webp 27 | - /images/projects/find-babycare/02_application _process.webp 28 | - /images/projects/find-babycare/03_application_dashboard.webp 29 | - /images/projects/find-babycare/04_application_management.webp 30 | - /images/projects/find-babycare/05_enrollment_management.webp 31 | legal: 32 | license: MIT License 33 | mainCopyrightOwner: 數位發展部 34 | repoOwner: 數位發展部 35 | maintenance: 36 | type: internal 37 | contacts: 38 | - name: 劉澄真 39 | email: "rosalind.liu@moda.gov.tw" 40 | phone: "02-23800325" 41 | localisation: 42 | localisationReady: false 43 | availableLanguages: 44 | - zh-Hant 45 | dependsOn: 46 | open: 47 | - name: PHP 48 | - name: MySQL 49 | tw: 50 | countryExtensionVersion: "1.0.0" 51 | techStacks: 52 | - name: "Vue.js" 53 | - name: "HTML/CSS" 54 | - name: "JavaScript" 55 | - name: "Laravel" 56 | vulnerabilityScanners: 57 | - "OWASP" 58 | # GitHub API 有資訊可以取得, 暫時手動填寫 59 | createdDate: "2023-11-22" 60 | -------------------------------------------------------------------------------- /publiccode-parser/projects/code-gov-tw.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: code-gov-tw 3 | url: "https://github.com/moda-gov-tw/code-gov-tw" 4 | landingURL: "https://code.gov.tw" 5 | releaseDate: "2024-04-29" 6 | platforms: 7 | - web 8 | categories: 9 | - data-collection 10 | - digital-citizenship 11 | developmentStatus: development 12 | softwareType: standalone/web 13 | description: 14 | zh-Hant: 15 | localisedName: 公共程式平臺 16 | shortDescription: > 17 | 公共程式平臺鼓勵跨機關、產業和公眾都可共同使用、 檢視、修正政府數位服務或系統的程式碼,將可促進機關數位交流,共同提升政府公共服務品質、提升政府公開透明。 18 | features: 19 | - 資料一覽 / 檢索 20 | legal: 21 | license: MIT 22 | mainCopyrightOwner: 數位發展部 民主網絡司 23 | repoOwner: 數位發展部 民主網絡司 24 | maintenance: 25 | type: internal 26 | contacts: 27 | - name: 劉澄真 28 | email: "rosalind.liu@moda.gov.tw" 29 | phone: "02-23800325" 30 | localisation: 31 | localisationReady: true 32 | availableLanguages: 33 | - zh-Hant 34 | dependsOn: 35 | open: 36 | - name: Node.js 37 | version: "18" 38 | usedBy: 39 | - "[公共程式平臺](https://code.gov.tw) (無障礙檢測結果:AAA)" 40 | tw: 41 | countryExtensionVersion: "1.0.0" 42 | techStacks: 43 | - name: "Qwik" 44 | - name: "Tailwind CSS" 45 | vulnerabilityScanners: 46 | - "GitHub CodeQL" 47 | - "GitHub Dependabot Alerts" 48 | - "OWASP ZAP" 49 | - "SonarQube" 50 | accessibilityConformance: "AAA" 51 | # GitHub API 有資訊可以取得, 暫時手動填寫 52 | createdDate: "2024-02-01" 53 | -------------------------------------------------------------------------------- /publiccode-parser/projects/gov-uk-forms-admin.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: gov-uk-forms-admin 3 | url: "https://github.com/moda-gov-tw/alphagov.forms-admin/" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: development 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: GOV.UK Forms Admin 15 | shortDescription: > 16 | GOV.UK Forms 是英國 GDS 政府數位服務團隊維運之公部門線上表單平台。此為該服務背後的管理軟體。此服務開放程式原始碼,可提供政府各機關與單位利用該平台之模版,建立可供外部填寫之簡易表單。 17 | 此服務具有自動檢查遞交內容功能,確認回填資料正確性;範本功能讓使用更便利;可追蹤表單遞交狀態,衡量表單的易用性。程式設計符合英國政府制定之網路安全標準,保護資料安全。 18 | features: 19 | - 表單填寫 / 資料上傳 20 | legal: 21 | license: MIT License 22 | mainCopyrightOwner: 英國政府數位服務團 Government Digital Service 23 | repoOwner: 數位發展部 民主網絡司 公民科技科 24 | maintenance: 25 | type: internal 26 | contacts: 27 | - name: 郭羽蓁 分析師 28 | email: "yujhen@moda.gov.tw" 29 | phone: "02-2380-0326" 30 | localisation: 31 | localisationReady: true 32 | availableLanguages: 33 | - en 34 | - zh-Hant 35 | dependsOn: 36 | open: 37 | - name: Ruby 38 | version: "3" 39 | - name: HTML/CSS 40 | applicationSuite: "GOV.UK Forms" 41 | isBasedOn: "https://github.com/alphagov/forms-admin" 42 | tw: 43 | countryExtensionVersion: "1.0.0" 44 | techStacks: 45 | - name: SCSS 46 | vulnerabilityScanners: 47 | - "本專案為在地化成果,未涉及軟體開發作業。" 48 | # GitHub API 有資訊可以取得, 暫時手動填寫 49 | createdDate: "2023-02-03" 50 | -------------------------------------------------------------------------------- /publiccode-parser/projects/gov-uk-forms-product-page.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: gov-uk-forms-product-pages 3 | url: "https://github.com/moda-gov-tw/alphagov.forms-product-page/" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: development 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: GOV.UK Forms Product Pages 15 | shortDescription: > 16 | GOV.UK Forms 是英國 GDS 政府數位服務團隊維運之公部門線上表單平台。此為該服務的頁面產生器。此服務開放程式原始碼,可提供政府各機關與單位利用該平台之模版,建立可供外部填寫之簡易表單。 17 | 此服務具有自動檢查遞交內容功能,確認回填資料正確性;範本功能讓使用更便利;可追蹤表單遞交狀態,衡量表單的易用性。程式設計符合英國政府制定之網路安全標準,保護資料安全。 18 | features: 19 | - 表單填寫 / 資料上傳 20 | legal: 21 | license: MIT License 22 | mainCopyrightOwner: 英國政府數位服務團 Government Digital Service 23 | repoOwner: 數位發展部 民主網絡司 公民科技科 24 | maintenance: 25 | type: internal 26 | contacts: 27 | - name: 郭羽蓁 分析師 28 | email: "yujhen@moda.gov.tw" 29 | phone: "02-2380-0326" 30 | localisation: 31 | localisationReady: true 32 | availableLanguages: 33 | - en 34 | - zh-Hant 35 | dependsOn: 36 | open: 37 | - name: HTML/CSS 38 | - name: Ruby 39 | version: "3" 40 | applicationSuite: "GOV.UK Forms" 41 | isBasedOn: "https://github.com/alphagov/forms-product-page" 42 | tw: 43 | countryExtensionVersion: "1.0.0" 44 | techStacks: 45 | - name: SCSS 46 | vulnerabilityScanners: 47 | - "本專案為在地化成果,未涉及軟體開發作業。" 48 | # GitHub API 有資訊可以取得, 暫時手動填寫 49 | createdDate: "2023-02-03" 50 | -------------------------------------------------------------------------------- /publiccode-parser/projects/gov-uk-notify.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: gov-uk-notifications-admin 3 | url: "https://github.com/moda-gov-tw/notifications-admin/" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - content-management 10 | - digital-citizenship 11 | developmentStatus: stable 12 | softwareType: standalone/web 13 | description: 14 | zh-Hant: 15 | localisedName: GOV.UK Notify 16 | shortDescription: > 17 | GOV.UK Notify 是英國 GDS 政府數位服務團隊維運的公部門業務訊息傳遞服務平台。業務訊息包含電子郵件、簡訊、信函等。此為該服務背後的 Admin 管理頁面。這服務讓使用者無需程式編碼能力也可以編輯預設訊息範本、追蹤訊息傳送狀態、手動或自動傳送訊息。此程式提供開放原始碼,各單位可以整合需要的功能至自己的業務系統或平台以增加訊息傳遞與通知的功能。 18 | features: 19 | - 公共服務通知 20 | legal: 21 | license: MIT License 22 | mainCopyrightOwner: 英國政府數位服務團 Government Digital Service 23 | repoOwner: 數位發展部 民主網絡司 公民科技科 24 | maintenance: 25 | type: internal 26 | contacts: 27 | - name: 郭羽蓁 分析師 28 | email: "yujhen@moda.gov.tw" 29 | phone: "02-2380-0326" 30 | localisation: 31 | localisationReady: true 32 | availableLanguages: 33 | - en 34 | - zh-Hant 35 | dependsOn: 36 | open: 37 | - name: Python 38 | version: "3.9" 39 | - name: HTML/CSS 40 | - name: JavaScript 41 | applicationSuite: "GOV.UK Notify" 42 | isBasedOn: "https://github.com/alphagov/notifications-admin" 43 | tw: 44 | countryExtensionVersion: "1.0.0" 45 | vulnerabilityScanners: 46 | - "本專案為在地化成果,未涉及軟體開發作業。" 47 | # GitHub API 有資訊可以取得, 暫時手動填寫 48 | createdDate: "2022-11-16" 49 | -------------------------------------------------------------------------------- /publiccode-parser/projects/italia-publiccode-yml-editor.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: italia-publiccode-editor 3 | url: "https://github.com/moda-gov-tw/italia-publiccode-editor" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: development 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: publiccode.yml Editor 15 | shortDescription: > 16 | 這個應用程式幫助您為您的專案創建並驗證一個 publiccode.yml 檔案。只需填寫表單即可生成一個符合最新標準的 YAML 檔案,然後您可以下載或複製到您的專案根目錄中。您也可以使用這個應用程式來檢查並糾正現有的 publiccode.yml 檔案。只需上傳檔案進行導入,應用程式將進行驗證並幫助修正任何問題。 17 | features: 18 | - 編輯器 19 | legal: 20 | license: AGPL-3.0 21 | mainCopyrightOwner: Foundation for Public Code 22 | repoOwner: 數位發展部 民主網絡司 公民科技科 23 | maintenance: 24 | type: internal 25 | contacts: 26 | - name: 郭羽蓁 分析師 27 | email: "yujhen@moda.gov.tw" 28 | phone: "02-2380-0326" 29 | localisation: 30 | localisationReady: true 31 | availableLanguages: 32 | - en 33 | - zh-Hant 34 | dependsOn: 35 | open: 36 | - name: HTML/CSS 37 | - name: JavaScript 38 | applicationSuite: "publiccode.yml" 39 | isBasedOn: "https://github.com/italia/publiccode-editor" 40 | tw: 41 | countryExtensionVersion: "1.0.0" 42 | techStacks: 43 | - name: SCSS 44 | vulnerabilityScanners: 45 | - "本專案為在地化成果,未涉及軟體開發作業。" 46 | # GitHub API 有資訊可以取得, 暫時手動填寫 47 | createdDate: "2022-12-01" 48 | -------------------------------------------------------------------------------- /publiccode-parser/projects/italia-publiccode-yml-standard.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: publiccodeyml-publiccode.yml 3 | url: "https://github.com/moda-gov-tw/publiccodeyml-publiccode.yml/" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: development 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: publiccode.yml Standard 15 | shortDescription: > 16 | 一種針對公共軟體的詮釋資料(metadata)描述標準,這標準對開發者以及技術背景較淺的人士都易於使用,目的是讓由公共行政機關和公共組織開發的軟體容易被發現。 17 | features: 18 | - 標準 19 | legal: 20 | license: CC0 v1.0 21 | mainCopyrightOwner: Foundation for Public Code 22 | repoOwner: 數位發展部 民主網絡司 公民科技科 23 | maintenance: 24 | type: internal 25 | contacts: 26 | - name: 郭羽蓁 分析師 27 | email: "yujhen@moda.gov.tw" 28 | phone: "02-2380-0326" 29 | localisation: 30 | localisationReady: true 31 | availableLanguages: 32 | - en 33 | - zh-Hant 34 | dependsOn: 35 | open: 36 | - name: Python 37 | applicationSuite: "publiccode.yml" 38 | isBasedOn: "https://github.com/publiccodeyml/publiccode.yml" 39 | usedBy: 40 | - "[公共程式平臺](https://code.gov.tw) (無障礙檢測結果:AAA)" 41 | tw: 42 | countryExtensionVersion: "1.0.0" 43 | vulnerabilityScanners: 44 | - "本專案為在地化成果,未涉及軟體開發作業。" 45 | # GitHub API 有資訊可以取得, 暫時手動填寫 46 | createdDate: "2022-12-01" 47 | -------------------------------------------------------------------------------- /publiccode-parser/projects/matrix-client-matrix-react-sdk.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: matrix-org.matrix-react-sdk 3 | url: "https://github.com/moda-gov-tw/matrix-org.matrix-react-sdk/" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: development 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: Matrix Client (Element Series) matrix-react-sdk 15 | shortDescription: > 16 | Element 是 Matrix 核心團隊基於去中心化標準開發的開放原始碼通訊軟體。此為該服務底層的 matrix-react-sdk ,可用於建置各種其他軟體。可應用網際網路 IP based 進行即時通訊。基於其開放特性,可整合其他程式,快速建構即時交換資料系統。除此之外,亦可與 Slack、Telegram 等其它通訊軟體進行串聯。 17 | features: 18 | - 即時通訊 19 | legal: 20 | license: Apache-2.0 21 | mainCopyrightOwner: Element 22 | repoOwner: 數位發展部 民主網絡司 公民科技科 23 | maintenance: 24 | type: internal 25 | contacts: 26 | - name: 郭羽蓁 分析師 27 | email: "yujhen@moda.gov.tw" 28 | phone: "02-2380-0326" 29 | localisation: 30 | localisationReady: true 31 | availableLanguages: 32 | - en 33 | - zh-Hant 34 | dependsOn: 35 | open: 36 | - name: HTML/CSS 37 | - name: JavaScript 38 | - name: TypeScript 39 | applicationSuite: "Matrix Element" 40 | isBasedOn: "https://github.com/matrix-org/matrix-react-sdk" 41 | tw: 42 | countryExtensionVersion: "1.0.0" 43 | vulnerabilityScanners: 44 | - "本專案為在地化成果,未涉及軟體開發作業。" 45 | techStacks: 46 | - name: "React" 47 | # GitHub API 有資訊可以取得, 暫時手動填寫 48 | createdDate: "2022-12-16" 49 | -------------------------------------------------------------------------------- /publiccode-parser/projects/matrix-client-vector-im.element-web.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: matrix-client-vector-im.element-web 3 | url: "https://github.com/moda-gov-tw/vector-im.element-web/" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: development 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: Matrix Client (Element Series) Web 15 | shortDescription: > 16 | Element 是 Matrix 核心團隊基於去中心化標準開發的開放原始碼通訊軟體。此為該服務基於 matrix-react-sdk 建置的網頁版軟體。可應用網際網路 IP based 進行即時通訊。基於其開放特性,可整合其他程式,快速建構即時交換資料系統。除此之外,亦可與 Slack、Telegram 等其它通訊軟體進行串聯。 17 | features: 18 | - 即時通訊 19 | legal: 20 | license: Apache-2.0 21 | mainCopyrightOwner: Element 22 | repoOwner: 數位發展部 民主網絡司 公民科技科 23 | maintenance: 24 | type: internal 25 | contacts: 26 | - name: 郭羽蓁 分析師 27 | email: "yujhen@moda.gov.tw" 28 | phone: "02-2380-0326" 29 | localisation: 30 | localisationReady: true 31 | availableLanguages: 32 | - en 33 | - zh-Hant 34 | dependsOn: 35 | open: 36 | - name: HTML/CSS 37 | - name: JavaScript 38 | - name: TypeScript 39 | applicationSuite: "Matrix Element" 40 | isBasedOn: "https://github.com/element-hq/element-web" 41 | tw: 42 | countryExtensionVersion: "1.0.0" 43 | techStacks: 44 | - name: "React" 45 | vulnerabilityScanners: 46 | - "本專案為在地化成果,未涉及軟體開發" 47 | # GitHub API 有資訊可以取得, 暫時手動填寫 48 | createdDate: "2022-12-16" 49 | -------------------------------------------------------------------------------- /publiccode-parser/projects/moda-ghg-inventory-system-frontend.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: moda-ghg-inventory-system-frontend 3 | url: "https://github.com/moda-gov-tw/moda-ghg-inventory-system-frontend" 4 | releaseDate: "2023-12-18" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: stable 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: 數位產業自願性溫室氣體盤查系統 15 | shortDescription: > 16 | 為落實臺灣2050淨零目標, 基於公共程式碼的理念,以公私協力方式發展可行之數位工具, 以數位部為首,示範將公共程式碼模式導入公部門及數位產業,推動政府開放資料、與民協作的倡議, 建立碳盤查系統公共程式,提供各界運用,降低自主碳盤查門檻。 17 | features: 18 | - 表單填寫 / 資料上傳 19 | - 個案管理 20 | - 數據呈現 21 | screenshots: 22 | - /images/projects/moda-ghg-inventory-system-frontend/screenshot.webp 23 | legal: 24 | license: 「網頁文字圖片採取 CC0 授權方式,程式碼採取 MIT 的授權方式」 25 | mainCopyrightOwner: 數位發展部 民主網路司 26 | repoOwner: 數位發展部 民主網路司 27 | maintenance: 28 | type: internal 29 | contacts: 30 | - name: 郭智宇 31 | email: "chihyukuo@moda.gov.tw" 32 | phone: "80345" 33 | localisation: 34 | localisationReady: false 35 | availableLanguages: 36 | - zh-Hant 37 | dependsOn: 38 | open: 39 | - name: ASP.NET Core 40 | version: "6.0" 41 | - name: C# 42 | - name: MySQL 43 | tw: 44 | countryExtensionVersion: "1.0.0" 45 | vulnerabilityScanners: 46 | - "HCL AppScan" 47 | createdDate: "2023-11-24" 48 | -------------------------------------------------------------------------------- /publiccode-parser/projects/moda-official-website-full.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: moda-official-website-full 3 | url: "https://github.com/moda-gov-tw/moda-official-website-full" 4 | landingURL: "https://moda.gov.tw/" 5 | releaseDate: "2024-04-16" 6 | platforms: 7 | - web 8 | categories: 9 | - data-collection 10 | - digital-citizenship 11 | developmentStatus: stable 12 | softwareType: standalone/web 13 | description: 14 | zh-Hant: 15 | localisedName: 數位發展部全球資訊網 16 | shortDescription: > 17 | 數位發展部全球資訊網,體現政府與民眾、政府與政府間傳遞數位發展規劃理念及施政成果的網站,是完整的內容管理系統(Content Management System);發展之初即規劃整體程式原始碼開放予各界,提供一套可以參考的解決方案,方便應用情境相似的機關團體 Fork 使用;網站以使用者為中心設計、符合網站無障礙規範、提供多國語系架構、支援全站靜態化。 18 | features: 19 | - 表單填寫 / 資料上傳 20 | - 資料一覽 / 檢索 21 | screenshots: 22 | - /images/projects/moda-official-website-full/screenshot-1.webp 23 | - /images/projects/moda-official-website-full/screenshot-2.webp 24 | - /images/projects/moda-official-website-full/screenshot-3.webp 25 | legal: 26 | license: MIT License 27 | mainCopyrightOwner: 數位發展部 資訊處 28 | repoOwner: 數位發展部 資訊處 29 | maintenance: 30 | type: internal 31 | contacts: 32 | - name: 許展銘 33 | email: "targee@moda.gov.tw" 34 | phone: "02-23800402" 35 | localisation: 36 | localisationReady: true 37 | availableLanguages: 38 | - zh-Hant 39 | - en 40 | 41 | dependsOn: 42 | open: 43 | - name: C# 44 | 45 | usedBy: 46 | - "[數位發展部全球資訊網](https://moda.gov.tw/) (無障礙檢測結果:AA) " 47 | 48 | tw: 49 | countryExtensionVersion: "1.0.0" 50 | # 這個專案所依賴的技術, 相較於 dependsOn, 這裡更細節的列出使用的技術 51 | techStacks: 52 | - name: "ASP.NET Core" 53 | - name: "SQL Server" 54 | - name: "JavaScript" 55 | - name: "HTML/CSS" 56 | vulnerabilityScanners: 57 | - "Checkmarx" 58 | accessibilityConformance: "AA" 59 | # GitHub API 有資訊可以取得, 暫時手動填寫 60 | createdDate: "2024-04-03" # YYYY-MM-DD, 這個專案的建立日期 61 | 62 | # 更多定義可以參考官方文件 https://yml.publiccode.tools/ 63 | -------------------------------------------------------------------------------- /publiccode-parser/projects/odf-application-tools.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: MODA_ODF_Application_Tools 3 | url: "https://github.com/MODAODF/MODA_ODF_Application_Tools" 4 | landingURL: "https://moda.gov.tw/digital-affairs/digital-service/app-services/248" 5 | releaseDate: "2024-01-23" 6 | platforms: 7 | - windows 8 | categories: 9 | - document-management 10 | - office 11 | developmentStatus: stable 12 | softwareType: addon 13 | description: 14 | zh-Hant: 15 | localisedName: ODF文件應用工具 16 | shortDescription: > 17 | 為引導公務機關從文件製作、保存均以開放文件格式(ODF)處理,且使政府ODF文件能跨機關流通,本部基於開放文件軟體,發展適合公務機關使用之「ODF文件應用工具」,提供編輯標準ODF文件及支援CNS11643中文標準交換碼全字庫,並提供 Q&A 問題回報程式,建立客服流程及諮詢服務,並將相關問題與回應提供其他使用者查詢。 18 | features: 19 | - 文書處理 20 | screenshots: 21 | - /images/projects/odf-application-tools/screenshot-1.webp 22 | - /images/projects/odf-application-tools/screenshot-2.webp 23 | - /images/projects/odf-application-tools/screenshot-3.webp 24 | legal: 25 | license: LGPL-3.0、MPL-2.0 26 | mainCopyrightOwner: 數位發展部 數位政府司 27 | repoOwner: 數位發展部 數位政府司 28 | maintenance: 29 | type: internal 30 | contacts: 31 | - name: 黃煌洲 32 | email: "huangchou@moda.gov.tw" 33 | phone: "02-2380-0225" 34 | localisation: 35 | localisationReady: false 36 | availableLanguages: 37 | - zh-Hant 38 | dependsOn: 39 | open: 40 | - name: Open Document Format 41 | - name: "C++" 42 | usedBy: 43 | - 各政府機關 44 | tw: 45 | countryExtensionVersion: "1.0.0" 46 | vulnerabilityScanners: 47 | - "SonarQube" 48 | # GitHub API 有資訊可以取得, 暫時手動填寫 49 | createdDate: "2022-03-06" 50 | -------------------------------------------------------------------------------- /publiccode-parser/projects/opendata-frontend.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: opendata-frontend 3 | url: "https://github.com/moda-gov-tw/opendata-frontend" 4 | releaseDate: "2024-07-31" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: stable 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: 政府資料開放平臺前端 15 | shortDescription: > 16 | 本專案為政府資料開放平臺前端開源,透過開源程式提供政府機關或民間團體,作為發展資料開放應用的參考,共同為政府開放資料盡一份心力。 17 | features: 18 | - 資料一覽 / 檢索 19 | screenshots: 20 | - /images/projects/opendata-frontend/screenshot-1.webp 21 | - /images/projects/opendata-frontend/screenshot-2.webp 22 | legal: 23 | license: MIT License 24 | mainCopyrightOwner: 數位發展部 多元創新司 25 | repoOwner: 數位發展部 多元創新司 26 | maintenance: 27 | type: internal 28 | contacts: 29 | - name: 曾華伶分析師 30 | email: "lynntseng@moda.gov.tw" 31 | phone: "02-23800392" 32 | localisation: 33 | localisationReady: true 34 | availableLanguages: 35 | - zh-Hant 36 | - en 37 | dependsOn: 38 | open: 39 | - name: JavaScript 40 | usedBy: 41 | - "[政府資料開放平臺](https://data.gov.tw/) (無障礙檢測結果:AA)" 42 | tw: 43 | countryExtensionVersion: "1.0.0" 44 | techStacks: 45 | - name: "HTML/CSS" 46 | - name: "TypeScript" 47 | - name: "Vue.js" 48 | vulnerabilityScanners: 49 | - "SonarQube" 50 | openapi: 51 | - name: 資料集列表 API 52 | description: "提供政府資料開放平臺資料集列表相關欄位,可藉由關鍵字進行簡易搜尋,亦可透過資料集名稱、檔案格式、標章類型、服務分類等條件進行進階搜尋。" 53 | url: "https://file.data.gov.tw/APIdocument_search.json" 54 | - name: 資料提供機關及服務分類選單 API 55 | description: "可運用資料提供機關及服務分類選單API,結合資料集列表API,開發現行政府資料開放平臺之資料提供機關及服務分類下拉式選單功能,便利選擇機關與服務分類,提供符合搜尋條件之相關資料集。" 56 | url: "https://file.data.gov.tw/APIdocument_options.json" 57 | # GitHub API 有資訊可以取得, 暫時手動填寫 58 | createdDate: "2024-04-30" 59 | -------------------------------------------------------------------------------- /publiccode-parser/projects/pdis-lighthouse.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: pdis/lighthouse 3 | url: "https://github.com/PDIS/lighthouse" 4 | landingURL: "https://lighthouse.pdis.run/" 5 | releaseDate: "2023-07-18" 6 | platforms: 7 | - web 8 | categories: 9 | - data-analytics 10 | developmentStatus: stable 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: Lighthouse 平臺 15 | shortDescription: > 16 | Lighthouse 平臺使用 GitHub Actions 自動執行 Lighthouse 掃描,將報告集中展示。 17 | Lighthouse 是一個開源的自動化工具,用於改善網頁的效能、品質和正確性。 18 | Lighthouse 對網頁進行一系列測試並產生一份報告及評分。透過報告,我們可以審視未通過測試的項目以了解如何改進。 19 | features: 20 | - 網頁效能檢測 21 | legal: 22 | license: CC0-1.0 23 | mainCopyrightOwner: 行政院公共數位創新空間(PDIS) 24 | repoOwner: 行政院公共數位創新空間(PDIS) 25 | maintenance: 26 | type: internal 27 | contacts: 28 | - name: 行政院公共數位創新空間(PDIS) 29 | email: "all@pdis.nat.gov.tw" 30 | phone: null 31 | localisation: 32 | localisationReady: false 33 | availableLanguages: 34 | - zh-Hant 35 | - en 36 | usedBy: 37 | - "[Lighthouse 平臺](https://lighthouse.pdis.run/)" 38 | tw: 39 | countryExtensionVersion: "1.0.0" 40 | techStacks: 41 | - name: "Lighthouse" 42 | vulnerabilityScanners: 43 | - "GitHub CodeQL" 44 | - "GitHub Dependabot Alerts" 45 | accessibilityConformance: null 46 | -------------------------------------------------------------------------------- /publiccode-parser/projects/pdis-web-jekyll.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: pdis/web-jekyll 3 | url: "https://github.com/PDIS/web-jekyll" 4 | landingURL: "https://pdis.nat.gov.tw/" 5 | releaseDate: "2017-09-05" 6 | platforms: 7 | - web 8 | categories: 9 | - blog 10 | developmentStatus: stable 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: 行政院公共數位創新空間(PDIS)部落格 15 | shortDescription: > 16 | 行政院公共數位創新空間(PDIS)部落格。 17 | 一套使用 Jekyll 靜態網站生成器,從純文字的 markdown 文件產生靜態 HTML 的系統。具備中、英文雙語系。 18 | features: 19 | - 靜態網站產生器 20 | - 自動翻譯 21 | legal: 22 | license: 除另有標示外,內容以創用 CC BY 4.0 授權條款釋出。 23 | mainCopyrightOwner: 行政院公共數位創新空間(PDIS) 24 | repoOwner: 行政院公共數位創新空間(PDIS) 25 | maintenance: 26 | type: internal 27 | contacts: 28 | - name: 行政院公共數位創新空間(PDIS) 29 | email: "all@pdis.nat.gov.tw" 30 | phone: null 31 | localisation: 32 | localisationReady: true 33 | availableLanguages: 34 | - zh-Hant 35 | - en 36 | usedBy: 37 | - "[行政院公共數位創新空間(PDIS)部落格](https://pdis.nat.gov.tw/)" 38 | tw: 39 | countryExtensionVersion: "1.0.0" 40 | techStacks: 41 | - name: "Jekyll" 42 | - name: "Cloud Translation API" 43 | vulnerabilityScanners: 44 | - "GitHub CodeQL" 45 | - "GitHub Dependabot Alerts" 46 | accessibilityConformance: null 47 | -------------------------------------------------------------------------------- /publiccode-parser/projects/publiccodenet.standard.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: publiccodenet.standard 3 | url: "https://github.com/moda-gov-tw/publiccodenet.standard/" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - web 7 | categories: 8 | - data-collection 9 | - digital-citizenship 10 | developmentStatus: development 11 | softwareType: standalone/web 12 | description: 13 | zh-Hant: 14 | localisedName: Standard for Public Code 15 | shortDescription: > 16 | Standard for Public Code 是由公共程式碼基金會(Foundation for Public Code)所制定的公共程式標準。目的為提供政府不同部門或協作組織之間,依循標準及指南開發軟體時,以利後續進行互相協作開發,並且重複使用各自的成果。此標準有利於公家機關透過協作方式,創造出好用、開放、易懂、課責、近用、永續的程式基底。 17 | features: 18 | - 標準 19 | legal: 20 | license: CC0 v1.0 21 | mainCopyrightOwner: Foundation for Public Code 22 | repoOwner: 數位發展部 民主網絡司 公民科技科 23 | maintenance: 24 | type: internal 25 | contacts: 26 | - name: 郭羽蓁 分析師 27 | email: "yujhen@moda.gov.tw" 28 | phone: "02-2380-0326" 29 | localisation: 30 | localisationReady: true 31 | availableLanguages: 32 | - en 33 | - zh-Hant 34 | dependsOn: 35 | open: 36 | - name: HTML/CSS 37 | - name: Shell 38 | applicationSuite: "Standard for Public Code" 39 | isBasedOn: "https://github.com/publiccodenet/standard" 40 | tw: 41 | countryExtensionVersion: "1.0.0" 42 | vulnerabilityScanners: 43 | - "本專案為在地化成果,未涉及軟體開發作業。" 44 | # GitHub API 有資訊可以取得, 暫時手動填寫 45 | createdDate: "2022-11-24" 46 | -------------------------------------------------------------------------------- /publiccode-parser/projects/taipei-city-dashboard.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: taipei-city-dashboard 3 | url: "https://github.com/tpe-doit/Taipei-City-Dashboard" 4 | landingURL: "https://citydashboard.taipei/" 5 | releaseDate: "2024-04-11" 6 | platforms: 7 | - web 8 | categories: 9 | - data-collection 10 | - digital-citizenship 11 | developmentStatus: stable 12 | softwareType: standalone/web 13 | description: 14 | zh-Hant: 15 | localisedName: 臺北城市儀表板 16 | shortDescription: > 17 | 臺北城市儀表板提供使用者進行圖資及數據圖表的交叉查閱,以資料時間、空間及統計等多維度趨勢呈現,並供使用者可針對關注的重點資料,自行客製主題頁面,讓使用者能在虛擬的三維圖台內,更即時的掌握城市脈動與分析相關數據。 18 | features: 19 | - 地圖資訊呈現 20 | - 數據呈現 21 | screenshots: 22 | - /images/projects/taipei-city-dashboard/screenshot-1.webp 23 | legal: 24 | license: AGPL-3 25 | mainCopyrightOwner: 臺北市政府資訊局 26 | repoOwner: 臺北市政府資訊局 27 | maintenance: 28 | type: internal 29 | contacts: 30 | - name: 詹士民 31 | email: "wp6223@gov.taipei" 32 | phone: "02-27208889 #51752" 33 | localisation: 34 | localisationReady: false 35 | availableLanguages: 36 | - zh-Hant 37 | dependsOn: 38 | open: 39 | - name: Python 40 | - name: Go 41 | - name: PostgreSQL 42 | - name: Redis 43 | usedBy: 44 | - "[臺北城市儀表板](https://citydashboard.taipei/) (無障礙檢測結果:尚未檢測)" 45 | tw: 46 | countryExtensionVersion: "1.0.0" 47 | techStacks: 48 | - name: "Vue.js" 49 | - name: "SQL Server" 50 | vulnerabilityScanners: 51 | - "Checkmarx" 52 | accessibilityConformance: "AA" 53 | # GitHub API 有資訊可以取得, 暫時手動填寫 54 | createdDate: "2023-05-22" # YYYY-MM-DD, 這個專案的建立日期 55 | -------------------------------------------------------------------------------- /publiccode-parser/projects/yivi-privacybydesign.irmamobile.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: privacybydesign.irmamobile 3 | url: "https://github.com/moda-gov-tw/privacybydesign.irmamobile/" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - android 7 | - ios 8 | categories: 9 | - data-collection 10 | - digital-citizenship 11 | developmentStatus: development 12 | softwareType: standalone/web 13 | description: 14 | zh-Hant: 15 | localisedName: Yivi (IRMA) 16 | shortDescription: > 17 | Yivi 是開放原始碼的身份驗證服務。此為該服務 App 的原始碼。此服務可因應不同需求,讓使用者可選擇不同程度的身份資料揭露。個人資料將以 PIN 碼加密的方式儲存在裝置中。使用者可以自行掌控個人資料,進行無密碼驗證登入網站、簽署文件等目的。 18 | features: 19 | - 身份驗證 20 | legal: 21 | license: GPL v3 22 | mainCopyrightOwner: Yivi 23 | repoOwner: 數位發展部 民主網絡司 公民科技科 24 | maintenance: 25 | type: internal 26 | contacts: 27 | - name: 郭羽蓁 分析師 28 | email: "yujhen@moda.gov.tw" 29 | phone: "02-2380-0326" 30 | localisation: 31 | localisationReady: true 32 | availableLanguages: 33 | - en 34 | - zh-Hant 35 | dependsOn: 36 | open: 37 | - name: Dart 38 | - name: Go 39 | - name: Java 40 | applicationSuite: "Yivi (IRMA)" 41 | isBasedOn: "https://github.com/privacybydesign/irmamobile" 42 | tw: 43 | countryExtensionVersion: "1.0.0" 44 | vulnerabilityScanners: 45 | - "本專案為在地化成果,未涉及軟體開發作業。" 46 | # GitHub API 有資訊可以取得, 暫時手動填寫 47 | createdDate: "2022-12-16" 48 | -------------------------------------------------------------------------------- /publiccode-parser/projects/yivi-privacybydesign.pbdf-schememanager.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: privacybydesign.pbdf-schememanager 3 | url: "https://github.com/moda-gov-tw/privacybydesign.pbdf-schememanager" 4 | releaseDate: "2023-11-08" 5 | platforms: 6 | - android 7 | - ios 8 | categories: 9 | - data-collection 10 | - digital-citizenship 11 | developmentStatus: development 12 | softwareType: standalone/web 13 | description: 14 | zh-Hant: 15 | localisedName: Yivi (IRMA) scheme manager 16 | shortDescription: > 17 | Yivi 是開放原始碼的身份驗證服務。此為該服務管理器的原始碼。因應不同需求,使用者可選擇不同程度的身份資料揭露。個人資料將以 PIN 碼加密的方式儲存在裝置中。使用者可以自行掌控個人資料,進行無密碼驗證登入網站、簽署文件等目的。 18 | features: 19 | - 身份驗證 20 | legal: 21 | license: GPL v3 22 | mainCopyrightOwner: Yivi 23 | repoOwner: 數位發展部 民主網絡司 公民科技科 24 | maintenance: 25 | type: internal 26 | contacts: 27 | - name: 郭羽蓁 分析師 28 | email: "yujhen@moda.gov.tw" 29 | phone: "02-2380-0326" 30 | localisation: 31 | localisationReady: true 32 | availableLanguages: 33 | - en 34 | - zh-Hant 35 | dependsOn: 36 | open: 37 | - name: Python 38 | - name: Shell 39 | applicationSuite: "Yivi (IRMA)" 40 | isBasedOn: "https://github.com/privacybydesign/pbdf-schememanager" 41 | tw: 42 | countryExtensionVersion: "1.0.0" 43 | vulnerabilityScanners: 44 | - "本專案為在地化成果,未涉及軟體開發作業。" 45 | # GitHub API 有資訊可以取得, 暫時手動填寫 46 | createdDate: "2022-12-20" 47 | -------------------------------------------------------------------------------- /publiccode-parser/template.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: public code platform 3 | url: "https://awesome.com" 4 | landingURL: "https://demo.awesome.com" 5 | releaseDate: "2022-03-29" # YYYY-MM-DD, 這個專案最新的版本發布日期 6 | platforms: 7 | - web 8 | categories: 9 | - data-collection 10 | - digital-citizenship 11 | developmentStatus: development 12 | softwareType: standalone/web 13 | description: 14 | # 網站目前預設使用的語系 15 | zh-Hant: 16 | localisedName: 公共程式平臺 # 顯示在網站上的名稱 17 | shortDescription: > 18 | 簡易描述, 最多只有150字 19 | # 詳細描述, 會顯示在細節頁面, 假如沒有填寫, 就會顯示簡易描述 20 | longDescription: > 21 | 詳細描述, 至少有150字以上 22 | features: 23 | - 表單填寫 / 資料上傳 24 | - 個案管理 25 | screenshots: 26 | - screenshots/screenshot1.png 27 | - screenshots/screenshot2.png 28 | # 可以使用多個語系, 但目前還沒支援 29 | en: 30 | shortDescription: > 31 | This is a short description of my awesome software, it is at most 150 words long 32 | longDescription: > 33 | This is a long description of my awesome software, it is at least 150 words long 34 | features: 35 | - Form filling/data uploading 36 | - Case Management 37 | screenshots: 38 | - screenshots/screenshot1.png 39 | - screenshots/screenshot2.png 40 | legal: 41 | license: MIT 42 | # 使用空白區分主要單位跟下屬單位, 網站上一覽頁面只顯示主要單位, 細節頁面才會顯示完整的提供者 43 | # 數位發展部<空白>民主網路司 44 | repoOwner: Mutix Co. 45 | maintenance: 46 | type: internal 47 | contacts: 48 | - name: someone's name 49 | email: "someone@email.com" 50 | phone: "+8861234567890" 51 | localisation: 52 | localisationReady: true 53 | availableLanguages: 54 | - zh-Hant 55 | - en 56 | # 這裡是寫系統層級依賴的技術 57 | dependsOn: # 這個專案所依賴的技術, 系統層級的 58 | open: # 開放原始碼的軟體 59 | - name: Node.js 60 | version: "18" 61 | usedBy: 62 | # 單純文字 63 | - Ministry of Awesome 64 | # 使用 Markdown 語法顯示網站連結 65 | - "[Ministry of Awesome, Ministry of Awesome 2](https://example.com) (無障礙檢測結果:尚未檢測)" 66 | tw: 67 | countryExtensionVersion: "1.0.0" 68 | # 這個專案所依賴的技術, 相較於 dependsOn, 這裡更細節的列出使用的技術 69 | techStacks: 70 | - name: "TypeScript" 71 | - name: "Qwik" 72 | - name: "Tailwind CSS" 73 | vulnerabilityScanners: 74 | - "OWASP" 75 | accessibilityConformance: "AA" 76 | # GitHub API 有資訊可以取得, 暫時手動填寫 77 | createdDate: "2024-02-01" # YYYY-MM-DD, 這個專案的建立日期 78 | 79 | # 更多定義可以參考官方文件 https://yml.publiccode.tools/ 80 | -------------------------------------------------------------------------------- /publiccode.yml: -------------------------------------------------------------------------------- 1 | publiccodeYmlVersion: 0.3.0 2 | name: code-gov-tw 3 | url: "https://github.com/moda-gov-tw/code-gov-tw" 4 | landingURL: "https://code.gov.tw" 5 | releaseDate: "2024-04-29" 6 | platforms: 7 | - web 8 | categories: 9 | - data-collection 10 | - digital-citizenship 11 | developmentStatus: development 12 | softwareType: standalone/web 13 | description: 14 | zh-Hant: 15 | localisedName: 公共程式平臺 16 | shortDescription: > 17 | 公共程式平臺鼓勵跨機關、產業和公眾都可共同使用、 檢視、修正政府數位服務或系統的程式碼,將可促進機關數位交流,共同提升政府公共服務品質、提升政府公開透明。 18 | features: 19 | - 資料一覽 / 檢索 20 | legal: 21 | license: MIT 22 | mainCopyrightOwner: 數位發展部 民主網絡司 23 | repoOwner: 數位發展部 民主網絡司 24 | maintenance: 25 | type: internal 26 | contacts: 27 | - name: 劉澄真 28 | email: "rosalind.liu@moda.gov.tw" 29 | phone: "02-23800325" 30 | localisation: 31 | localisationReady: true 32 | availableLanguages: 33 | - zh-Hant 34 | dependsOn: 35 | open: 36 | - name: Node.js 37 | version: "18" 38 | usedBy: 39 | - "[公共程式平臺](https://code.gov.tw) (無障礙檢測結果:AAA)" 40 | tw: 41 | countryExtensionVersion: "1.0.0" 42 | techStacks: 43 | - name: "Qwik" 44 | - name: "Tailwind CSS" 45 | vulnerabilityScanners: 46 | - "GitHub CodeQL" 47 | - "GitHub Dependabot Alerts" 48 | - "OWASP ZAP" 49 | - "SonarQube" 50 | accessibilityConformance: "AAA" 51 | # GitHub API 有資訊可以取得, 暫時手動填寫 52 | createdDate: "2024-02-01" 53 | -------------------------------------------------------------------------------- /src/components/breadcrumb/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Link from "~/components/link"; 3 | 4 | type Props = { 5 | pages?: { title: string; href: string; current?: boolean }[]; 6 | }; 7 | 8 | export default component$(({ pages = [] }) => { 9 | return ( 10 | 58 | ); 59 | }); 60 | -------------------------------------------------------------------------------- /src/components/button/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$, Slot } from "@builder.io/qwik"; 2 | import type { LinkProps } from "@builder.io/qwik-city"; 3 | import Link from "~/components/link"; 4 | 5 | export default component$((props) => { 6 | return ( 7 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | ); 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/footer/contact.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import MODALogo from "~/media/logos/MODA-logo.svg?jsx"; 3 | 4 | export default component$(() => { 5 | return ( 6 |
7 | {$localize`數位發展部`} 8 | {$localize`100057 臺北市中正區延平南路143號`} 9 | 10 | {$localize`民意信箱: `} 11 | 17 | www-mailbox.moda.gov.tw 18 | 19 | 20 | {$localize`更新日期:2024-04-30`} 21 | 22 | 27 | CC0 28 | 29 | No copyright reserved. 30 | 31 | 32 | 33 | 34 |
35 | ); 36 | }); 37 | -------------------------------------------------------------------------------- /src/components/footer/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Section from "../section"; 3 | import Contact from "./contact"; 4 | import SiteMap from "./site-map"; 5 | 6 | export default component$(() => { 7 | return ( 8 |
9 |
10 |
{$localize`公共程式平臺`}
11 |
12 | 13 | 14 |
15 |
16 |
17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/footer/site-map.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Link from "~/components/link"; 3 | 4 | export default component$(() => { 5 | return ( 6 |
7 |
8 |
{$localize`關於公共程式`}
9 |
10 | 11 |

{$localize`公共程式一覽`}

12 | 13 | 14 |

{$localize`認識公共程式`}

15 | 16 | 17 |

{$localize`未來規劃`}

18 | 19 |
20 |
21 | 22 |
23 |
{$localize`參與公共程式`}
24 |
25 | 26 |

{$localize`提供公共程式`}

27 | 28 | 29 |

{$localize`如何參與`}

30 | 31 |
32 |
33 |
34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$, useSignal } from "@builder.io/qwik"; 2 | import Link from "~/components/link"; 3 | import NavAbout from "./nav-about"; 4 | import NavLanguage from "./nav-language"; 5 | import NavMobileAbout from "./nav-mobile-about"; 6 | import NavMobileLanguage from "./nav-mobile-language"; 7 | import Logo from "./logo.png?jsx"; 8 | 9 | export default component$(() => { 10 | const menu = useSignal(false); 11 | 12 | return ( 13 |
14 | 58 |
59 |
60 |
61 |
62 |
63 | 64 | 65 |
66 |
公共程式平臺
67 |
code.gov.tw
68 |
69 | 70 |
71 | 92 |
93 |
94 |
95 |
96 | 97 | 101 | {$localize`公共程式一覽`} 102 | 103 |
104 |
105 | 106 |
107 |
108 |
109 |
110 |
111 |
112 | ); 113 | }); 114 | -------------------------------------------------------------------------------- /src/components/header/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/components/header/logo.png -------------------------------------------------------------------------------- /src/components/header/nav-about.tsx: -------------------------------------------------------------------------------- 1 | import { $, component$, useSignal } from "@builder.io/qwik"; 2 | import { Link } from "@builder.io/qwik-city"; 3 | 4 | export default component$(() => { 5 | const menu = useSignal(false); 6 | 7 | const onClick$ = $(() => (menu.value = !menu.value)); 8 | 9 | return ( 10 |
11 | 31 |
42 |
43 |
44 | 49 | {$localize`認識公共程式`} 50 | 51 | 52 |
53 |
54 | 55 | {$localize`未來規劃`} 56 | 57 | 58 |
59 |
60 |
61 |
62 | ); 63 | }); 64 | -------------------------------------------------------------------------------- /src/components/header/nav-language.tsx: -------------------------------------------------------------------------------- 1 | import { $, component$, useSignal } from "@builder.io/qwik"; 2 | import Link from "~/components/link"; 3 | 4 | export default component$(() => { 5 | const menu = useSignal(false); 6 | 7 | const onClick$ = $(() => (menu.value = !menu.value)); 8 | 9 | return ( 10 | 58 | ); 59 | }); 60 | -------------------------------------------------------------------------------- /src/components/header/nav-link.tsx: -------------------------------------------------------------------------------- 1 | import { Slot, component$ } from "@builder.io/qwik"; 2 | import { Link, type LinkProps } from "@builder.io/qwik-city"; 3 | 4 | type NavLinkProps = LinkProps & { isActive?: boolean }; 5 | 6 | export default component$(({ isActive, ...props }: NavLinkProps) => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /src/components/header/nav-mobile-about.tsx: -------------------------------------------------------------------------------- 1 | import { component$, useSignal } from "@builder.io/qwik"; 2 | import Link from "../link"; 3 | 4 | export default component$(() => { 5 | const menu = useSignal(false); 6 | 7 | return ( 8 |
9 | 32 |
36 | 40 | {$localize`認識公共程式`} 41 | 42 | 46 | {$localize`未來規劃`} 47 | 48 |
49 |
50 | ); 51 | }); 52 | -------------------------------------------------------------------------------- /src/components/header/nav-mobile-language.tsx: -------------------------------------------------------------------------------- 1 | import { component$, useSignal } from "@builder.io/qwik"; 2 | 3 | export default component$(() => { 4 | const menu = useSignal(false); 5 | 6 | return ( 7 | 49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /src/components/link/index.tsx: -------------------------------------------------------------------------------- 1 | import { Slot, component$, useSignal, $, useTask$ } from "@builder.io/qwik"; 2 | import { Link, useLocation, type LinkProps } from "@builder.io/qwik-city"; 3 | import useLocaleLink from "./useLocaleLink"; 4 | 5 | export default component$((props: LinkProps) => { 6 | const base = useLocaleLink(); 7 | const href = `${base}${props.href}`; 8 | 9 | const state = useSignal(false); 10 | const location = useLocation(); 11 | 12 | // Reset the state when navigation is done. 13 | useTask$(({ track }) => { 14 | track(() => location.isNavigating); 15 | if (!location.isNavigating) { 16 | state.value = false; 17 | } 18 | }); 19 | 20 | // Set the state to true when the link is clicked. 21 | const onClick = $(() => { 22 | state.value = true; 23 | }); 24 | 25 | return ( 26 | 32 | 33 | 39 | 40 | ); 41 | }); 42 | -------------------------------------------------------------------------------- /src/components/link/useLocaleLink.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from "@builder.io/qwik-city"; 2 | 3 | export default function useLocaleLink() { 4 | const location = useLocation(); 5 | const locale = location.params.locale; 6 | const baselink = locale ? `/${locale}` : ""; 7 | 8 | return baselink; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/promotion/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Section from "~/components/section"; 3 | import ArrowRightIcon from "~/media/icons/arrow-right-icon.svg?jsx"; 4 | import Button from "~/components/button"; 5 | 6 | export default component$(() => { 7 | return ( 8 |
9 |
10 | 23 |
24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /src/components/router-head/router-head.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import { useDocumentHead, useLocation } from "@builder.io/qwik-city"; 3 | 4 | /** 5 | * The RouterHead component is placed inside of the document `` element. 6 | */ 7 | export const RouterHead = component$(() => { 8 | const head = useDocumentHead(); 9 | const loc = useLocation(); 10 | 11 | return ( 12 | <> 13 | 14 | {head.title || 15 | "Public Code Platform — 數位發展部 Ministry of Digital Affairs"} 16 | 17 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 34 | 35 | 36 | 37 | {head.meta.map((m) => ( 38 | 39 | ))} 40 | {head.links.map((l) => ( 41 | 42 | ))} 43 | 44 | ); 45 | }); 46 | -------------------------------------------------------------------------------- /src/components/section/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$, Slot, type ClassList } from "@builder.io/qwik"; 2 | 3 | type SectionProps = { 4 | class?: ClassList; 5 | }; 6 | 7 | export default component$((props) => { 8 | return ( 9 |
10 |
15 | 16 |
17 |
18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /src/contexts/theme-context.ts: -------------------------------------------------------------------------------- 1 | import { type Signal } from "@builder.io/qwik"; 2 | import { useContext, createContextId } from "@builder.io/qwik"; 3 | 4 | export enum Theme { 5 | light = "light", 6 | dark = "dark", 7 | } 8 | 9 | export const ThemeContext = createContextId>("theme-context"); 10 | 11 | export const UserThemeContext = 12 | createContextId>("user.theme-context"); 13 | 14 | export const useTheme = () => { 15 | const theme = useContext(ThemeContext); 16 | const userTheme = useContext(UserThemeContext); 17 | return userTheme.value || theme.value; 18 | }; 19 | -------------------------------------------------------------------------------- /src/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/data/.gitkeep -------------------------------------------------------------------------------- /src/entry.dev.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * WHAT IS THIS FILE? 3 | * 4 | * Development entry point using only client-side modules: 5 | * - Do not use this mode in production! 6 | * - No SSR 7 | * - No portion of the application is pre-rendered on the server. 8 | * - All of the application is running eagerly in the browser. 9 | * - More code is transferred to the browser than in SSR mode. 10 | * - Optimizer/Serialization/Deserialization code is not exercised! 11 | */ 12 | import { render, type RenderOptions } from "@builder.io/qwik"; 13 | import Root from "./root"; 14 | 15 | export default function (opts: RenderOptions) { 16 | return render(document, , opts); 17 | } 18 | -------------------------------------------------------------------------------- /src/entry.preview.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * WHAT IS THIS FILE? 3 | * 4 | * It's the bundle entry point for `npm run preview`. 5 | * That is, serving your app built in production mode. 6 | * 7 | * Feel free to modify this file, but don't remove it! 8 | * 9 | * Learn more about Vite's preview command: 10 | * - https://vitejs.dev/config/preview-options.html#preview-options 11 | * 12 | */ 13 | import { createQwikCity } from "@builder.io/qwik-city/middleware/node"; 14 | import qwikCityPlan from "@qwik-city-plan"; 15 | import render from "./entry.ssr"; 16 | 17 | /** 18 | * The default export is the QwikCity adapter used by Vite preview. 19 | */ 20 | export default createQwikCity({ render, qwikCityPlan }); 21 | -------------------------------------------------------------------------------- /src/entry.ssr.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * WHAT IS THIS FILE? 3 | * 4 | * SSR entry point, in all cases the application is rendered outside the browser, this 5 | * entry point will be the common one. 6 | * 7 | * - Server (express, cloudflare...) 8 | * - npm run start 9 | * - npm run preview 10 | * - npm run build 11 | * 12 | */ 13 | import { 14 | renderToStream, 15 | type RenderToStreamOptions, 16 | } from "@builder.io/qwik/server"; 17 | import { manifest } from "@qwik-client-manifest"; 18 | import Root from "./root"; 19 | import { extractBase } from "./i18n-utils"; 20 | 21 | export default function (opts: RenderToStreamOptions) { 22 | return renderToStream(, { 23 | manifest, 24 | ...opts, 25 | base: extractBase, // determine the base URL for the client code 26 | // Use container attributes to set attributes on the html tag. 27 | containerAttributes: { 28 | lang: opts.serverData!.locale || "zh-Hant", 29 | ...opts.containerAttributes, 30 | }, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/global.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Tailwind CSS imports 3 | * View the full documentation at https://tailwindcss.com 4 | */ 5 | @tailwind base; 6 | @tailwind components; 7 | @tailwind utilities; 8 | 9 | @layer components { 10 | .max-wrapper { 11 | box-sizing: content-box; 12 | max-width: 80rem; 13 | margin: 0 auto; 14 | } 15 | } 16 | 17 | @layer base { 18 | h1 { 19 | @apply text-5xl font-medium; 20 | } 21 | 22 | .h1-sub { 23 | @apply text-2xl font-medium; 24 | } 25 | 26 | h2 { 27 | @apply text-3xl font-medium; 28 | } 29 | 30 | .h2-sub { 31 | @apply text-lg font-medium; 32 | } 33 | 34 | h3 { 35 | @apply text-2xl font-medium; 36 | } 37 | 38 | h4 { 39 | @apply text-lg font-medium; 40 | } 41 | 42 | small { 43 | @apply text-sm font-normal; 44 | } 45 | } 46 | 47 | :root { 48 | view-transition-name: none; 49 | } 50 | 51 | html { 52 | @apply text-base font-normal leading-relaxed text-black; 53 | -webkit-text-size-adjust: 100%; 54 | -moz-tab-size: 4; 55 | -o-tab-size: 4; 56 | tab-size: 4; 57 | font-family: 58 | "PingFang TC", 59 | "Noto Sans TC", 60 | system-ui, 61 | -apple-system, 62 | "Segoe UI", 63 | Roboto, 64 | "Helvetica Neue", 65 | Arial, 66 | "Noto Sans", 67 | sans-serif, 68 | "Apple Color Emoji", 69 | "Segoe UI Emoji", 70 | "Segoe UI Symbol", 71 | "Noto Color Emoji"; 72 | } 73 | 74 | @media (max-width: 768px) { 75 | h1 { 76 | @apply text-4xl; 77 | } 78 | 79 | .h1-sub { 80 | @apply text-xl; 81 | } 82 | 83 | h2 { 84 | @apply text-2xl; 85 | } 86 | 87 | .h2-sub { 88 | @apply text-lg; 89 | } 90 | 91 | h3 { 92 | @apply text-xl; 93 | } 94 | 95 | h4 { 96 | @apply text-lg; 97 | } 98 | 99 | small { 100 | @apply text-sm; 101 | } 102 | } 103 | 104 | body { 105 | padding: 0; 106 | line-height: inherit; 107 | } 108 | 109 | @page { 110 | size: 380mm 500mm; 111 | margin: 5mm 14mm; 112 | } 113 | 114 | @media print { 115 | html, 116 | body { 117 | width: 380mm; 118 | height: 500mm; 119 | } 120 | } 121 | 122 | header { 123 | position: relative; 124 | } 125 | 126 | @keyframes expand { 127 | 0% { 128 | width: 0%; 129 | } 130 | 100% { 131 | width: 100%; 132 | } 133 | } 134 | 135 | @layer utilities { 136 | .animate-expand { 137 | animation: expand 3s ease-in-out infinite; 138 | } 139 | } 140 | 141 | .loading-icon { 142 | width: 20px; 143 | height: 20px; 144 | border: 5px solid #d3420e; 145 | border-top: 5px solid white; 146 | border-radius: 50%; 147 | } 148 | -------------------------------------------------------------------------------- /src/i18n-utils.ts: -------------------------------------------------------------------------------- 1 | import "@angular/localize/init"; 2 | import { loadTranslations } from "@angular/localize"; 3 | import { $, getLocale, useOnDocument, withLocale } from "@builder.io/qwik"; 4 | import type { RenderOptions } from "@builder.io/qwik/server"; 5 | 6 | // You must declare all your locales here 7 | import EN from "./locales/message.en.json"; 8 | import ZHHant from "./locales/message.zh-Hant.json"; 9 | 10 | // Make sure it's obvious when the default locale was selected 11 | const DEFAULT_LOCALE = "zh-Hant"; 12 | 13 | /** 14 | * This file is left for the developer to customize to get the behavior they want for localization. 15 | */ 16 | 17 | /// Declare location where extra types will be stored. 18 | const $localizeFn = $localize as any as { 19 | TRANSLATIONS: Record; 20 | TRANSLATION_BY_LOCALE: Map>; 21 | }; 22 | 23 | /** 24 | * This solution uses the `@angular/localize` package for translations, however out of the box 25 | * `$localize` works with a single translation only. This code adds support for multiple locales 26 | * concurrently. It does this by intercepting the `TRANSLATIONS` property read and returning 27 | * appropriate translation based on the current locale which is store in the `usEnvDate('local')`. 28 | */ 29 | 30 | if (!$localizeFn.TRANSLATION_BY_LOCALE) { 31 | $localizeFn.TRANSLATION_BY_LOCALE = new Map([["", {}]]); 32 | Object.defineProperty($localize, "TRANSLATIONS", { 33 | get: function () { 34 | const locale = getLocale(DEFAULT_LOCALE); 35 | let translations = $localizeFn.TRANSLATION_BY_LOCALE.get(locale); 36 | if (!translations) { 37 | $localizeFn.TRANSLATION_BY_LOCALE.set(locale, (translations = {})); 38 | } 39 | return translations; 40 | }, 41 | }); 42 | } 43 | 44 | /** 45 | * Function used to load all translations variants. 46 | */ 47 | export function initTranslations() { 48 | [ZHHant, EN].forEach(({ translations, locale }) => { 49 | withLocale(locale, () => loadTranslations(translations)); 50 | }); 51 | } 52 | 53 | /** 54 | * Function used to examine the request and determine the locale to use. 55 | * 56 | * This function is meant to be used with `RenderOptions.locale` property 57 | * 58 | * @returns The locale to use which will be stored in the `useEnvData('locale')`. 59 | */ 60 | export function extractLang(locale: string): string { 61 | return locale && $localizeFn.TRANSLATION_BY_LOCALE.has(locale) 62 | ? locale 63 | : DEFAULT_LOCALE; 64 | } 65 | 66 | /** 67 | * Function used to determine the base URL to use for loading the chunks in the browser. 68 | * 69 | * The function returns `/build` in dev mode or `/build/` in prod mode. 70 | * 71 | * This function is meant to be used with `RenderOptions.base` property 72 | * 73 | * @returns The base URL to use for loading the chunks in the browser. 74 | */ 75 | export function extractBase({ serverData }: RenderOptions): string { 76 | if (import.meta.env.DEV) { 77 | return "/build"; 78 | } else { 79 | return "/build/" + serverData!.locale; 80 | } 81 | } 82 | 83 | export function useI18n() { 84 | if (import.meta.env.DEV) { 85 | // During development only, load all translations in memory when the app starts on the client. 86 | // eslint-disable-next-line 87 | useOnDocument("qinit", $(initTranslations)); 88 | } 89 | } 90 | 91 | // We always need the translations on the server 92 | if (import.meta.env.SSR) { 93 | initTranslations(); 94 | } 95 | -------------------------------------------------------------------------------- /src/media/icons/arrow-left-icon.svg: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /src/media/icons/arrow-right-icon.svg: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /src/media/icons/arrow-top-right-on-square.svg: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | -------------------------------------------------------------------------------- /src/media/icons/bars3-icon.svg: -------------------------------------------------------------------------------- 1 | 9 | 15 | -------------------------------------------------------------------------------- /src/media/icons/chevron-down-icon.svg: -------------------------------------------------------------------------------- 1 | 8 | 13 | -------------------------------------------------------------------------------- /src/media/icons/chevron-left-icon-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/media/icons/chevron-left-icon.svg: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /src/media/icons/chevron-right-icon-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/media/icons/chevron-right-icon.svg: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /src/media/icons/chevron-up-icon.svg: -------------------------------------------------------------------------------- 1 | 8 | 13 | -------------------------------------------------------------------------------- /src/media/icons/exclamation-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/media/icons/funnel-icon.svg: -------------------------------------------------------------------------------- 1 | 7 | 12 | -------------------------------------------------------------------------------- /src/media/icons/question-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/media/icons/x-mark-icon.svg: -------------------------------------------------------------------------------- 1 | 9 | 15 | -------------------------------------------------------------------------------- /src/media/images/about-feature-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/about-feature-01.png -------------------------------------------------------------------------------- /src/media/images/about-feature-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/about-feature-02.png -------------------------------------------------------------------------------- /src/media/images/about-feature-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/about-feature-03.png -------------------------------------------------------------------------------- /src/media/images/about-feature-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/about-feature-04.png -------------------------------------------------------------------------------- /src/media/images/about-work-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/about-work-all.png -------------------------------------------------------------------------------- /src/media/images/future-law.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/future-law.png -------------------------------------------------------------------------------- /src/media/images/future-platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/future-platform.png -------------------------------------------------------------------------------- /src/media/images/future-talent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/future-talent.png -------------------------------------------------------------------------------- /src/media/images/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/hero.png -------------------------------------------------------------------------------- /src/media/images/index-eco-system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/index-eco-system.png -------------------------------------------------------------------------------- /src/media/images/index-info-card-gov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/index-info-card-gov.png -------------------------------------------------------------------------------- /src/media/images/index-info-card-people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/index-info-card-people.png -------------------------------------------------------------------------------- /src/media/images/index-what-is-public-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/index-what-is-public-code.png -------------------------------------------------------------------------------- /src/media/images/participate-screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/participate-screenshot-1.png -------------------------------------------------------------------------------- /src/media/images/participate-screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moda-gov-tw/code-gov-tw/3f323772a32896b171b17841b569bc05f7cf4035/src/media/images/participate-screenshot-2.png -------------------------------------------------------------------------------- /src/root.tsx: -------------------------------------------------------------------------------- 1 | import { component$, useServerData } from "@builder.io/qwik"; 2 | import { 3 | QwikCityProvider, 4 | RouterOutlet, 5 | ServiceWorkerRegister, 6 | } from "@builder.io/qwik-city"; 7 | import { RouterHead } from "./components/router-head/router-head"; 8 | import { useI18n } from "./i18n-utils"; 9 | import "./global.css"; 10 | 11 | export default component$(() => { 12 | const nonce = useServerData("nonce"); 13 | /** 14 | * The root of a QwikCity site always start with the component, 15 | * immediately followed by the document's and . 16 | * 17 | * Don't remove the `` and `` elements. 18 | */ 19 | const csp = [ 20 | `default-src 'self'`, 21 | `font-src 'self' data:`, 22 | `img-src 'self' data:`, 23 | `script-src 'self' 'unsafe-eval' '${nonce ? `nonce-${nonce}` : "unsafe-inline"}'`, 24 | `style-src 'self' '${nonce ? `nonce-${nonce}` : "unsafe-inline"}'`, 25 | `frame-src 'self'`, 26 | `object-src 'none'`, 27 | `base-uri 'self'`, 28 | `form-action 'self'`, 29 | ]; 30 | useI18n(); 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | }); 46 | -------------------------------------------------------------------------------- /src/routes/404.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Section from "~/components/section"; 3 | import WhatIsPublicCode from "~/media/images/index-what-is-public-code.png?jsx"; 4 | import useLocaleLink from "~/components/link/useLocaleLink"; 5 | 6 | export default component$(() => { 7 | const baselink = useLocaleLink(); 8 | 9 | return ( 10 |
11 |
18 | 23 | 24 |
25 |

404

26 |
27 |

28 | 您所查詢的網址無法顯示,可能是因為您所查詢的網頁或檔案不存在或已被移除,請至 29 | {/* 30 | */} 31 | 32 | 網站首頁 33 | 34 | {/* 35 | */} 36 | 查詢您所需要的資訊。 37 |

38 |

39 | The webpage you are trying to access either does not exist or has 40 | been removed, Please visit the 41 | {/* 42 | */} 43 | 44 | {" "} 45 | home page 46 | {" "} 47 | {/* 48 | */} 49 | to find the information you need. 50 |

51 |
52 |
53 |
54 |
55 | ); 56 | }); 57 | -------------------------------------------------------------------------------- /src/routes/[locale]/404.tsx: -------------------------------------------------------------------------------- 1 | import PageNotFound from "../404"; 2 | 3 | export default PageNotFound; 4 | -------------------------------------------------------------------------------- /src/routes/[locale]/about/index.tsx: -------------------------------------------------------------------------------- 1 | import { type StaticGenerateHandler } from "@builder.io/qwik-city"; 2 | import About from "~/routes/about"; 3 | 4 | export const onStaticGenerate: StaticGenerateHandler = async () => { 5 | return { 6 | params: [{ locale: "en", param: "about" }], 7 | }; 8 | }; 9 | 10 | export default About; 11 | -------------------------------------------------------------------------------- /src/routes/[locale]/future-plan/index.tsx: -------------------------------------------------------------------------------- 1 | import FuturePlan from "~/routes/future-plan"; 2 | 3 | import { type StaticGenerateHandler } from "@builder.io/qwik-city"; 4 | 5 | export const onStaticGenerate: StaticGenerateHandler = async () => { 6 | return { 7 | params: [{ locale: "en", param: "future-plan" }], 8 | }; 9 | }; 10 | 11 | export default FuturePlan; 12 | -------------------------------------------------------------------------------- /src/routes/[locale]/index.tsx: -------------------------------------------------------------------------------- 1 | import { type StaticGenerateHandler } from "@builder.io/qwik-city"; 2 | import Index from "~/routes"; 3 | 4 | export const onStaticGenerate: StaticGenerateHandler = async () => { 5 | return { 6 | params: [{ locale: "en" }], 7 | }; 8 | }; 9 | 10 | export default Index; 11 | -------------------------------------------------------------------------------- /src/routes/[locale]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { component$, Slot } from "@builder.io/qwik"; 2 | import type { RequestHandler } from "@builder.io/qwik-city"; 3 | import { extractLang } from "~/i18n-utils"; 4 | 5 | export const onRequest: RequestHandler = ({ locale, params }) => { 6 | locale(extractLang(params.locale)); 7 | }; 8 | 9 | export default component$(() => { 10 | return ; 11 | }); 12 | -------------------------------------------------------------------------------- /src/routes/[locale]/participate/index.tsx: -------------------------------------------------------------------------------- 1 | import Participate from "../../participate"; 2 | import { type StaticGenerateHandler } from "@builder.io/qwik-city"; 3 | 4 | export const onStaticGenerate: StaticGenerateHandler = async () => { 5 | return { 6 | params: [{ locale: "en", param: "participate" }], 7 | }; 8 | }; 9 | 10 | export default Participate; 11 | -------------------------------------------------------------------------------- /src/routes/[locale]/projects/[repo]/index.tsx: -------------------------------------------------------------------------------- 1 | import IndividualPublicCode from "../../../projects/[repo]"; 2 | import { type StaticGenerateHandler } from "@builder.io/qwik-city"; 3 | import projects from "~/data/projects.json"; 4 | 5 | export const onStaticGenerate: StaticGenerateHandler = async () => { 6 | const length = projects.length; 7 | const paths = []; 8 | for (let i = 0; i < length; i++) { 9 | paths.push({ locale: "en", repo: `${i}` }); 10 | } 11 | return { 12 | params: paths, 13 | }; 14 | }; 15 | export default IndividualPublicCode; 16 | -------------------------------------------------------------------------------- /src/routes/[locale]/projects/index.tsx: -------------------------------------------------------------------------------- 1 | import Projects from "~/routes/projects"; 2 | import { type StaticGenerateHandler } from "@builder.io/qwik-city"; 3 | 4 | export const onStaticGenerate: StaticGenerateHandler = async () => { 5 | return { 6 | params: [{ locale: "en", param: "projects" }], 7 | }; 8 | }; 9 | 10 | export default Projects; 11 | -------------------------------------------------------------------------------- /src/routes/[locale]/submit/index.tsx: -------------------------------------------------------------------------------- 1 | import Submit from "../../submit"; 2 | import { type StaticGenerateHandler } from "@builder.io/qwik-city"; 3 | 4 | export const onStaticGenerate: StaticGenerateHandler = async () => { 5 | return { 6 | params: [{ locale: "en", param: "submit" }], 7 | }; 8 | }; 9 | 10 | export default Submit; 11 | -------------------------------------------------------------------------------- /src/routes/about/accordion.tsx: -------------------------------------------------------------------------------- 1 | import { component$, useSignal } from "@builder.io/qwik"; 2 | import ChevronUpIcon from "~/media/icons/chevron-up-icon.svg?jsx"; 3 | import ChevronDownIcon from "~/media/icons/chevron-down-icon.svg?jsx"; 4 | 5 | type AccordionProps = { 6 | title: string; 7 | description: string; 8 | }; 9 | 10 | export default component$(({ title, description }) => { 11 | const isOpen = useSignal(false); 12 | return ( 13 |
14 | 27 | {isOpen.value && ( 28 |

29 | {description} 30 |

31 | )} 32 |
33 | ); 34 | }); 35 | -------------------------------------------------------------------------------- /src/routes/about/accordions.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Accordion from "./accordion"; 3 | 4 | export default component$(() => { 5 | const accordions = [ 6 | { 7 | title: $localize`當系統的程式碼被開放檢視/使用,會不會有資安問題?`, 8 | description: $localize`公共程式只有開放系統架構,並不開放內涵資料。且因為可公開檢視,當系統有漏洞時,有更多人可以共同找尋問題,更可以防止系統開發時撰寫不正當之程式碼。`, 9 | }, 10 | { 11 | title: $localize`使用公共程式與現在的資訊系統開發方式,有什麼不一樣?`, 12 | description: $localize`一般的資訊系統開發時,通常需從頭定義需求並開發。但使用公共程式時,可以奠基於已經定義過需求及開發完成的資訊系統,進行客製化使用。`, 13 | }, 14 | { 15 | title: $localize`什麼樣的資訊廠商可以協助公共程式的客製化開發?`, 16 | description: $localize`因為是公開程式碼,所以任何廠商都可以接手做客製化與維護,開啟政府機關與更多不同廠商合作的可能性。`, 17 | }, 18 | { 19 | title: $localize`對於沒有執行過公共程式開發標案的承辦或廠商,會不會提供相關協助?`, 20 | description: $localize`除了年底將發布的公共程式參考指引,針對不熟悉公共程式的機關承辦與廠商,也將開設培力課程。`, 21 | }, 22 | ]; 23 | 24 | return ( 25 |
26 | 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /src/routes/about/features.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Feature1 from "~/media/images/about-feature-01.png?jsx"; 3 | import Feature2 from "~/media/images/about-feature-02.png?jsx"; 4 | import Feature3 from "~/media/images/about-feature-03.png?jsx"; 5 | import Feature4 from "~/media/images/about-feature-04.png?jsx"; 6 | 7 | export default component$(() => { 8 | const features = [ 9 | { 10 | title: $localize`協助前期對焦`, 11 | title2: $localize`讓共識的形成更加快速`, 12 | description: $localize`公共程式為具體、已開發的資訊系統。各政府機關能以公共程式為討論基礎,有效統整需求,進而提出明確規格,提升與資訊廠商的溝通品質。`, 13 | image: Feature1, 14 | imageAlt: "Assistance with Initial Focusing", 15 | }, 16 | { 17 | title: $localize`資源共享`, 18 | title2: $localize`善用已開發的資訊系統`, 19 | description: $localize`過去有共同需求的機關可以利用相同系統,減少重複開發的心力。 使用公共程式,透過共享前段開發成果,將精神集中在客製化該公共程式。`, 20 | image: Feature2, 21 | imageAlt: "Resource Sharing", 22 | }, 23 | { 24 | title: $localize`公開釋出`, 25 | title2: $localize`信任來自理解與看見`, 26 | description: $localize`系統程式碼進行開放,資訊服務的品質即可被公開檢視。尤其當資訊服務牽涉到公眾利益,公共程式的透明將讓公眾理解系統如何運作,並因此帶來信任。`, 27 | image: Feature3, 28 | imageAlt: "Public Release", 29 | }, 30 | { 31 | title: $localize`加速政府數位化`, 32 | title2: $localize`拓展政府數位服務範圍`, 33 | description: $localize`透過公共程式,提供不同資訊系統的示範,讓各政府機關獲得數位化公共服務的靈感,與廠商共創,促進更多優良資訊系統的架設。`, 34 | image: Feature4, 35 | imageAlt: "Accelerating Government Digitalization", 36 | }, 37 | ]; 38 | 39 | return ( 40 |
41 | 61 | ); 62 | }); 63 | -------------------------------------------------------------------------------- /src/routes/about/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Section from "~/components/section"; 3 | import Breadcrumb from "~/components/breadcrumb"; 4 | import Promotion from "~/components/promotion"; 5 | import PublicCodeWork from "./public-code-work"; 6 | import Features from "./features"; 7 | import Accordions from "./accordions"; 8 | import AboutGlobal from "~/media/images/about-global.svg?jsx"; 9 | 10 | export default component$(() => { 11 | return ( 12 | <> 13 |
14 | 22 |

{$localize`認識公共程式`}

23 |
24 | {$localize`讓資訊系統的開發變得更有效率,優良政府數位服務可以更簡單地發生`} 25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 | 37 |

{$localize`歐洲自由軟體基金會(FSFE)在 2017 年提出了一項重要主張:認為政府所建置的系統應當完全公開,並提供清晰的指引和規範。此為「公共資金,公共程式」(public money, public code)運動。`}

38 |

39 | {$localize`而國際上著名的公共程式跨國案例為`} 40 | 45 | X-Road 46 | 47 | {$localize`,由愛沙尼亞、芬蘭和冰島政府共同營運。除了各國政府能夠自由使用功能以外,例如在補助申請和駕照換發等功能,因為公共程式的特性,各國將都可以公開檢視與提供建議,並一旦發現問題,即可立即提出修正並將反饋納入系統中,並且讓所有單位同步更新。`} 48 |

49 |

{$localize`除此之外,許多歐洲、北美和澳大利亞等國家也積極鼓勵將軟體開發成果以開源方式釋出,以促進公共程式的理念。`}

50 |
51 | 52 |
53 |
54 |
55 | 56 |
57 |
58 | 59 |
60 | 61 | 62 | ); 63 | }); 64 | -------------------------------------------------------------------------------- /src/routes/about/public-code-work.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import WorkAll from "~/media/images/about-work-all.png?jsx"; 3 | 4 | export default component$(() => { 5 | const features = [ 6 | { 7 | title: $localize`01 參考公共程式`, 8 | description: $localize`政府機關欲開發資訊服務時,可於公共程式一覽瀏覽公共程式,查看是否已存在類似功能的公共程式。`, 9 | }, 10 | { 11 | title: $localize`02 公共程式加值利用`, 12 | description: $localize`與廠商合作,在既存的公共程式碼基礎上,進行實際需求的客製化開發。`, 13 | }, 14 | { 15 | title: $localize`03 分享開發成果`, 16 | description: $localize`開發完成後,確認授權方式,並再次開放公共程式。公共程式不僅能成為示範,再為其他機關沿用,亦可開放民間,讓公共再次回到公共。`, 17 | }, 18 | ]; 19 | 20 | return ( 21 |
22 | 42 | ); 43 | }); 44 | -------------------------------------------------------------------------------- /src/routes/future-plan/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Section from "~/components/section"; 3 | import Breadcrumb from "~/components/breadcrumb"; 4 | import Promotion from "~/components/promotion"; 5 | import FuturePlatform from "~/media/images/future-platform.png?jsx"; 6 | import FutureLaw from "~/media/images/future-law.png?jsx"; 7 | import FutureTalent from "~/media/images/future-talent.png?jsx"; 8 | 9 | export default component$(() => { 10 | const features = [ 11 | { 12 | image: FuturePlatform, 13 | imageAlt: "Future Platform", 14 | title: $localize`方便查找的公共程式`, 15 | description: $localize`為了方便查詢與使用,無論是來自數位發展部的示範案例,還是政府內各個機關開放的公共程式,甚至國外提供開放的資訊服務程式碼,都將儲存收整在單一窗口,即公共程式平臺(code.gov.tw)。所有政策公告也將一致在此發佈,讓民間或政府需要了解或使用公共程式的人,易於了解。`, 16 | }, 17 | { 18 | image: FutureLaw, 19 | imageAlt: "Future Law", 20 | title: $localize`建置相關規範與指引`, 21 | description: $localize`調適規範與指引,讓現行制度更容易導入公共程式,包括減少公共程式政策與相關法規的歧異,提供政府機關實務推動的依據。另外也將完備行政院及所屬機關(構)公共程式參考指引,包括公務機關辦理公共程式業務的規劃、採購、管理、資通安全防護等指南。`, 22 | }, 23 | { 24 | image: FutureTalent, 25 | imageAlt: "Future Talent", 26 | title: $localize`打造人才培育環境`, 27 | description: $localize`透過教育訓練與宣導推廣,鼓勵民間有意投入者,在此參與公共程式的開發、維護和營運。另外將推動公共程式培力方案:包含試辦政府採購案、獎助公共程式研發和開辦培訓課程,並研擬回饋機制。`, 28 | }, 29 | ]; 30 | const roadmap = [ 31 | { 32 | date: "2024/04", 33 | content: $localize`code.gov.tw 正式上線,並預計持續調整網頁功能,以更符合網站使用者的需求`, 34 | }, 35 | { 36 | date: "2024/12", 37 | content: $localize`發布 113 年公民科技試驗場域成果,提供更多示範公共程式`, 38 | }, 39 | { 40 | date: "2025/05", 41 | content: $localize`發布公共程式指引,以提供公共程式標案指南`, 42 | }, 43 | { 44 | date: "2025/12", 45 | content: $localize`發布我國政府軟體建置與開放原始碼現況報告`, 46 | }, 47 | ]; 48 | 49 | return ( 50 | <> 51 |
52 |
53 |
54 | 62 |

{$localize`未來規劃`}

63 |
64 |
65 |
66 |
67 |
68 |
69 | 75 |
76 | {features.map((feature) => ( 77 |
81 |
82 | 83 |
84 |

{feature.title}

85 |

{feature.description}

86 |
87 |
88 |
89 | ))} 90 |
91 |
92 |
93 |
94 |
95 | 109 |
110 | 111 | 112 | ); 113 | }); 114 | -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Section from "~/components/section"; 3 | import Promotion from "~/components/promotion"; 4 | import Button from "~/components/button"; 5 | import HeroSvg from "~/media/images/hero.png?jsx"; 6 | import WhatIsPublicCode from "~/media/images/index-what-is-public-code.png?jsx"; 7 | import InfoCardGOV from "~/media/images/index-info-card-gov.png?jsx"; 8 | import InfoCardPeople from "~/media/images/index-info-card-people.png?jsx"; 9 | import ECOSystem from "~/media/images/index-eco-system.png?jsx"; 10 | import ArrowRightIcon from "~/media/icons/arrow-right-icon.svg?jsx"; 11 | 12 | export default component$(() => { 13 | return ( 14 | <> 15 |
16 |
17 |
18 |

{$localize`取之公共,用之公共`}

19 |

{$localize`集結眾人之力,提升政府數位服務品質`}

20 |

21 | {$localize`科技不斷進步,越來越多政府的服務都在網路上進行。如果政府機關之間可以分享各自在製作網站、打造 App 等數位服務時的成果,就可以帶來集體智慧的優勢。公共程式是一種能被不同政府機關間共用的電腦程式,它的透明共享將有機會連結地方與中央、政府與民間,集結眾人之力,釋放政府潛能。`} 22 |

23 |

24 | {$localize`因此,公共程式 (Public code) 是一種不可或缺的數位基礎建設,讓政府的數位環境邁向下個科技時代。`} 25 |

26 |
27 | 31 |
32 |
33 |
34 |
35 |
36 | 40 |
41 | 50 |
51 |
52 |
53 | 54 |

55 | {$localize`我想進一步了解公共程式`} 56 |

57 | 61 |
62 |
63 | 64 |

65 | {$localize`我是有意願協作或給建議`} 66 |

67 |

68 | {$localize`想知道可以怎麼參與`} 69 |

70 | 74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 89 | 93 |
94 | 98 |
99 |
100 | 101 | 102 | ); 103 | }); 104 | -------------------------------------------------------------------------------- /src/routes/layout.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | $, 3 | component$, 4 | Slot, 5 | useSignal, 6 | useContextProvider, 7 | useOnWindow, 8 | } from "@builder.io/qwik"; 9 | import { type RequestHandler } from "@builder.io/qwik-city"; 10 | import { isDev } from "@builder.io/qwik/build"; 11 | import Footer from "~/components/footer"; 12 | import Header from "~/components/header"; 13 | import { 14 | Theme, 15 | ThemeContext, 16 | UserThemeContext, 17 | } from "~/contexts/theme-context"; 18 | 19 | export const onGet: RequestHandler = async ({ cacheControl }) => { 20 | // Control caching for this request for best performance and to reduce hosting costs: 21 | // https://qwik.builder.io/docs/caching/ 22 | cacheControl({ 23 | // Always serve a cached response by default, up to a week stale 24 | staleWhileRevalidate: 60 * 60 * 24 * 7, 25 | // Max once every 5 seconds, revalidate on the server to get a fresh version of this page 26 | maxAge: 5, 27 | }); 28 | }; 29 | 30 | export const onRequest: RequestHandler = (event) => { 31 | const nonce = "q9Wxu3i-H-RYysHVncGjiFv__BwHb7DzNVfUVr4gxlQ"; 32 | if (!isDev) event.sharedMap.set("@nonce", nonce); 33 | }; 34 | 35 | export default component$(() => { 36 | const theme = useSignal(Theme.light); 37 | const userTheme = useSignal(null); 38 | 39 | useContextProvider(ThemeContext, theme); 40 | useContextProvider(UserThemeContext, userTheme); 41 | 42 | useOnWindow( 43 | "DOMContentLoaded", 44 | $(() => { 45 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 46 | if (window.matchMedia) { 47 | const darkMedia = window.matchMedia("(prefers-color-scheme: dark)"); 48 | 49 | theme.value = darkMedia.matches ? Theme.dark : Theme.light; 50 | 51 | darkMedia.addEventListener("change", (event: MediaQueryListEvent) => { 52 | theme.value = event.matches ? Theme.dark : Theme.light; 53 | }); 54 | } 55 | 56 | const localTheme = localStorage.getItem("theme"); 57 | if (localTheme === Theme.dark) userTheme.value = Theme.dark; 58 | else if (localTheme === Theme.light) userTheme.value = Theme.light; 59 | }), 60 | ); 61 | 62 | const tValue = userTheme.value || theme.value; 63 | return ( 64 |
65 |
66 |
67 | 68 |
69 |
70 |
71 | ); 72 | }); 73 | -------------------------------------------------------------------------------- /src/routes/participate/block.tsx: -------------------------------------------------------------------------------- 1 | import { component$, Slot } from "@builder.io/qwik"; 2 | 3 | type BlockProps = { 4 | title: string; 5 | }; 6 | 7 | export default component$(({ title }) => { 8 | return ( 9 |
10 |
{title}
11 |
12 | 13 |
14 | {/* full-width backgroud color for mobile view */} 15 | 16 |
17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /src/routes/participate/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Section from "~/components/section"; 3 | import Promotion from "~/components/promotion"; 4 | import ParticipateScreenshot1 from "~/media/images/participate-screenshot-1.png?jsx"; 5 | import ParticipateScreenshot2 from "~/media/images/participate-screenshot-2.png?jsx"; 6 | import Block from "./block"; 7 | 8 | export default component$(() => { 9 | return ( 10 | <> 11 |
12 |

{$localize`如何參與`}

13 |
14 | 15 |
16 |
17 | 23 |
24 | 25 |
26 | {$localize`Issue 是協作平臺上的一種重要功能,類似網路論壇的討論區。讓有權限的協作者,可以針對特定程式提出意見與討論。無論是回報錯誤、建議新功能,甚至只是提問或尋求幫助,都可以透過 issue 完成。以下是如何建立 issue 的說明方式:`} 27 |
28 |
{$localize`1. 註冊或登錄協作平臺。`}
29 |
30 | {$localize`2. 在想參與的公共程式頁面裡,找到「Issues」標籤,並點擊右上角的「New Issue」。`} 31 | 35 |
36 |
37 | {$localize`3. 填寫 Issue 的標題和詳細描述,盡可能清晰地說明您想提出的意見,或是遇到的問題。如果項目中已經有相關討論串,則可以按照討論串上的指引,加入回覆。填寫完畢後請點擊「Submit new issue」,進行提交。`} 38 | 42 |
43 |
44 | {$localize`4. 送出 issue 後,會由該公共程式的管理人來回應,敬請耐心等候。`} 45 |
46 |
47 | 48 |
49 | {$localize`Pull Request 是協作平臺中的一種通知機制,允許專業協作者告訴其他人有一段代碼已經完成修改。目前各政府機關於協作平臺上儲存的公共程式,提供不同程度的參與形式。因此若是您欲投入專業協作,包括進行代碼審查、討論和改進,請直接前往想參與的公共程式,查看是否開放 Pull Request。`} 50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 | 71 |
72 | 73 | 74 | ); 75 | }); 76 | -------------------------------------------------------------------------------- /src/routes/projects/[repo]/carousel.tsx: -------------------------------------------------------------------------------- 1 | import { component$, useSignal, $ } from "@builder.io/qwik"; 2 | import ChevronLeftIcon from "~/media/icons/chevron-left-icon-light.svg?jsx"; 3 | import ChevronRightIcon from "~/media/icons/chevron-right-icon-light.svg?jsx"; 4 | 5 | type CarouselProps = { 6 | images: string[]; 7 | }; 8 | 9 | export default component$(({ images }) => { 10 | const containerRef = useSignal(); 11 | const pointsRef = useSignal(); 12 | const currentIndex = useSignal(0); 13 | const isMoving = useSignal(false); 14 | const startX = useSignal(0); 15 | 16 | const nextSlide = $(() => { 17 | currentIndex.value = (currentIndex.value + 1) % images.length; 18 | }); 19 | 20 | const prevSlide = $(() => { 21 | currentIndex.value = 22 | (currentIndex.value - 1 + images.length) % images.length; 23 | }); 24 | 25 | const handleTouchStart = $((e: TouchEvent) => { 26 | isMoving.value = true; 27 | startX.value = e.touches[0].clientX; 28 | }); 29 | 30 | const handleTouchEnd = $(() => { 31 | isMoving.value = false; 32 | }); 33 | 34 | const handleTouchMove = $((e: TouchEvent) => { 35 | if (!isMoving.value) return; 36 | const currentX = e.touches[0].clientX; 37 | const diff = startX.value - currentX; 38 | if (diff > 100) { 39 | nextSlide(); 40 | isMoving.value = false; 41 | } 42 | if (diff < -100) { 43 | prevSlide(); 44 | isMoving.value = false; 45 | } 46 | }); 47 | 48 | const handleMouseDown = $((e: MouseEvent) => { 49 | isMoving.value = true; 50 | startX.value = e.clientX; 51 | }); 52 | const handleMouseUp = $(() => { 53 | isMoving.value = false; 54 | if (containerRef.value) containerRef.value.style.transform = ""; 55 | }); 56 | const handleMouseMove = $((e: MouseEvent) => { 57 | if (!isMoving.value) return; 58 | const currentX = e.clientX; 59 | const diff = startX.value - currentX; 60 | if (containerRef.value) 61 | containerRef.value.style.transform = `translateX(${-diff}px)`; 62 | if (diff > 100) { 63 | nextSlide(); 64 | isMoving.value = false; // Add this line to prevent moving more than one slide 65 | } 66 | if (diff < -100) { 67 | prevSlide(); 68 | isMoving.value = false; // Add this line to prevent moving more than one slide 69 | } 70 | }); 71 | 72 | return ( 73 |
74 |
75 | {images.length > 1 && ( 76 | 83 | )} 84 |
94 |
95 | {images.map((imageSrc, index) => ( 96 |
103 | {`Screenshot 111 |
112 | ))} 113 |
114 |
115 | {images.length > 1 && ( 116 | 123 | )} 124 |
125 |
126 | {images.map((_, index) => ( 127 |
134 | ))} 135 |
136 |
137 | ); 138 | }); 139 | -------------------------------------------------------------------------------- /src/routes/projects/[repo]/list.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | 3 | const extractMarkdownLink = (content: string) => { 4 | if (!content || content === "") return null; 5 | const start = content.indexOf("["); 6 | const end = content.indexOf("]"); 7 | if (start !== -1 && end !== -1 && start < end) { 8 | const title = content.substring(start + 1, end); 9 | const hrefStart = content.indexOf("(", end); 10 | const hrefEnd = content.indexOf(")", hrefStart); 11 | if (hrefStart !== -1 && hrefEnd !== -1 && hrefStart < hrefEnd) { 12 | const href = content.substring(hrefStart + 1, hrefEnd); 13 | return { title, href }; 14 | } 15 | } 16 | return null; 17 | }; 18 | 19 | type ListProps = { 20 | title: string; 21 | contents?: (string | null | undefined)[]; 22 | useMarkdown?: boolean; 23 | }; 24 | 25 | export default component$((props) => { 26 | if (!props.contents) { 27 | return null; 28 | } 29 | 30 | const renderContent = (content: string | null | undefined, index: number) => { 31 | if (!content) return null; 32 | const markdownLink = extractMarkdownLink(content); 33 | if (props.useMarkdown && markdownLink) { 34 | const { title, href } = markdownLink; 35 | const other = content.replace(`[${title}](${href})`, ""); 36 | return ( 37 |
38 | 44 | {title} 45 | 46 | {other} 47 |
48 | ); 49 | } 50 | return
{content}
; 51 | }; 52 | 53 | return ( 54 |
55 |
{props.title}
56 |
{props.contents.map(renderContent)}
57 |
58 | ); 59 | }); 60 | -------------------------------------------------------------------------------- /src/routes/projects/[repo]/openapi.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Button from "~/components/button"; 3 | import ArrowTopRightOnSquare from "~/media/icons/arrow-top-right-on-square.svg?jsx"; 4 | 5 | type OpenAPIProps = { 6 | name: string; 7 | description: string; 8 | url: string; 9 | }; 10 | 11 | export default component$((props) => { 12 | return ( 13 |
14 |
15 |

{props.name}

16 |
{props.description}
17 | 24 |
25 |
26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /src/routes/projects/filter-checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { component$, useSignal, $, type QRL } from "@builder.io/qwik"; 2 | 3 | type Props = { 4 | key: string; 5 | option: string; 6 | filterName: string; 7 | onChange$: QRL<(index: number, state: boolean) => void>; 8 | checked: boolean; 9 | index: number; 10 | }; 11 | 12 | export default component$((props) => { 13 | const loading = useSignal(false); 14 | 15 | const handleFilterChange = $(async (e: Event) => { 16 | const target = e.target as HTMLInputElement; // Cast the event target to HTMLInputElement 17 | loading.value = true; 18 | await props.onChange$(props.index, target.checked); 19 | loading.value = false; 20 | }); 21 | 22 | return ( 23 |
24 | 35 | 38 |
44 |
45 | ); 46 | }); 47 | -------------------------------------------------------------------------------- /src/routes/projects/filter.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | component$, 3 | useStore, 4 | $, 5 | useSignal, 6 | useOnDocument, 7 | } from "@builder.io/qwik"; 8 | import FilterCheckbox from "./filter-checkbox"; 9 | import { useLocation } from "@builder.io/qwik-city"; 10 | 11 | type FilterProps = { 12 | filterName: string; 13 | categoryName: string; 14 | filterOptions: string[]; 15 | store?: any; 16 | }; 17 | 18 | export default component$((props) => { 19 | const location = useLocation(); 20 | const filterSize = useSignal(0); 21 | 22 | const filters = useStore({ 23 | status: new Array(props.filterOptions.length).fill(false), 24 | }); 25 | 26 | const updateQueryParameters = $((seletedFilter: string[]) => { 27 | // Workaround for the issue that the query parameters are not extracted in useLocation 28 | const queryParameters = new URLSearchParams(document.location.search); 29 | if (seletedFilter.length === 0) { 30 | queryParameters.delete(props.filterName); 31 | // Remove the query parameter if there are no filters selected 32 | const final = 33 | queryParameters.size === 0 34 | ? location.url.pathname 35 | : `?${queryParameters}`; 36 | window.history.replaceState({}, "", final); 37 | return; 38 | } 39 | 40 | queryParameters.set(props.filterName, seletedFilter.join(",")); 41 | window.history.replaceState({}, "", `?${queryParameters}`); 42 | }); 43 | 44 | const updateFilters = $(() => { 45 | const seletedFilter = [] as string[]; 46 | filters.status.forEach((status, index) => { 47 | if (status) { 48 | seletedFilter.push(props.filterOptions[index]); 49 | } 50 | }); 51 | 52 | props.store[props.filterName] = seletedFilter; 53 | updateQueryParameters(seletedFilter); 54 | filterSize.value = seletedFilter.length; 55 | }); 56 | 57 | const initQueryParameters = $(() => { 58 | // Workaround for the issue that the query parameters are not extracted in useLocation 59 | const queryParameters = new URLSearchParams(document.location.search); 60 | const selectedFilters = queryParameters.get(props.filterName); 61 | if (selectedFilters) { 62 | const selectedFiltersArray = selectedFilters.split(","); 63 | selectedFiltersArray.forEach((selectedFilter) => { 64 | const index = props.filterOptions.indexOf(selectedFilter); 65 | if (index !== -1) { 66 | filters.status[index] = true; 67 | } 68 | }); 69 | updateQueryParameters(selectedFiltersArray); 70 | updateFilters(); 71 | filterSize.value = selectedFiltersArray.length; 72 | } 73 | }); 74 | 75 | useOnDocument("DOMContentLoaded", initQueryParameters); 76 | 77 | const handleFilterChange = $((index: number, state: boolean) => { 78 | filters.status[index] = state; 79 | updateFilters(); 80 | }); 81 | 82 | const handleCheckAll = $(() => { 83 | filters.status.forEach((_, index) => { 84 | filters.status[index] = true; 85 | }); 86 | updateFilters(); 87 | }); 88 | 89 | const handleClearFilters = $(() => { 90 | filters.status.forEach((_, index) => { 91 | filters.status[index] = false; 92 | }); 93 | updateFilters(); 94 | }); 95 | 96 | return ( 97 |
102 |
103 |
104 |

{props.categoryName}

105 |
106 | {filterSize.value < 2 ? ( 107 | 110 | ) : ( 111 | 114 | )} 115 |
116 |
117 | {props.filterOptions.map((option, index) => { 118 | return ( 119 | 127 | ); 128 | })} 129 |
130 |
131 | ); 132 | }); 133 | -------------------------------------------------------------------------------- /src/routes/projects/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | component$, 3 | useSignal, 4 | useStore, 5 | useComputed$, 6 | $, 7 | useTask$, 8 | } from "@builder.io/qwik"; 9 | import Section from "~/components/section"; 10 | import { PageNav } from "~/routes/projects/page-nav"; 11 | import Filter from "~/routes/projects/filter"; 12 | import projectData from "~/data/projects.json"; 13 | import filters from "~/data/filters.json"; 14 | import projectIndex from "~/data/index.json"; 15 | import MobileFilterClose from "./mobile-filter-close"; 16 | import MobileFilterOpen from "./mobile-filter-open"; 17 | import RepoList from "./repo-list"; 18 | 19 | function paginateData( 20 | filteredData: number[], 21 | pageNumber: number, 22 | itemsPerPage: number, 23 | ): number[] { 24 | const startIndex = (pageNumber - 1) * itemsPerPage; 25 | const endIndex = startIndex + itemsPerPage; 26 | return filteredData.slice(startIndex, endIndex); 27 | } 28 | 29 | export default component$(() => { 30 | const mobileFilterStatus = useSignal(false); 31 | const itemsPerPage = 5; 32 | const currentPage = useSignal(1); 33 | const filterStore = useStore({ 34 | features: [], 35 | repoOwners: [], 36 | techStacks: [], 37 | }); 38 | 39 | useTask$(({ track }) => { 40 | track(() => filterStore.features); 41 | track(() => filterStore.repoOwners); 42 | track(() => filterStore.techStacks); 43 | 44 | currentPage.value = 1; 45 | }); 46 | 47 | const computedProjects = useComputed$( 48 | (): { data: number[]; total: number } => { 49 | const projectList: number[] = []; 50 | filterStore.features.forEach((feature) => { 51 | const target = projectIndex.features[feature] as number[]; 52 | projectList.push(...target); 53 | }); 54 | filterStore.repoOwners.forEach((repoOwner) => { 55 | const target = projectIndex.repoOwners[repoOwner] as number[]; 56 | projectList.push(...target); 57 | }); 58 | filterStore.techStacks.forEach((techStack) => { 59 | const target = projectIndex.techStacks[techStack] as number[]; 60 | projectList.push(...target); 61 | }); 62 | 63 | let uniqueProjectList = [...new Set(projectList)]; 64 | 65 | // If no filters are selected, show all projects 66 | if (uniqueProjectList.length === 0) { 67 | uniqueProjectList = [...Array.from(projectData.keys())]; 68 | } 69 | 70 | const pages = paginateData( 71 | uniqueProjectList, 72 | currentPage.value, 73 | itemsPerPage, 74 | ); 75 | 76 | return { 77 | data: pages, 78 | total: uniqueProjectList.length, 79 | }; 80 | }, 81 | ); 82 | 83 | const mainFilterHandler = $(async () => { 84 | mobileFilterStatus.value = !mobileFilterStatus.value; 85 | }); 86 | 87 | return ( 88 | <> 89 |
90 |

{$localize`公共程式專案一覽`}

91 |
92 | {$localize`公共程式由各政府單位提供,以下匯集國內外不同單位的公共程式。`} 93 |
94 |
95 | 99 | 103 |
104 |
105 |
111 |
112 | 118 | 124 | 130 |
131 |
132 |
133 |
134 | 135 | 140 |
141 |
142 |
143 |
144 | 145 | ); 146 | }); 147 | -------------------------------------------------------------------------------- /src/routes/projects/label.tsx: -------------------------------------------------------------------------------- 1 | import { component$, Slot } from "@builder.io/qwik"; 2 | 3 | export default component$(() => { 4 | return ( 5 |
6 |
7 | 8 |
9 |
10 | ); 11 | }); 12 | -------------------------------------------------------------------------------- /src/routes/projects/mobile-filter-close.tsx: -------------------------------------------------------------------------------- 1 | import { type QRL, component$, $, useSignal } from "@builder.io/qwik"; 2 | 3 | type Props = { 4 | display: boolean; 5 | onClick$: QRL<() => void>; 6 | }; 7 | 8 | export default component$((props) => { 9 | const loading = useSignal(false); 10 | 11 | const handleButtonClick = $(async () => { 12 | loading.value = true; 13 | await props.onClick$(); 14 | loading.value = false; 15 | }); 16 | 17 | return ( 18 |
24 |

{$localize`設定篩選條件`}

25 | 41 |
42 | ); 43 | }); 44 | -------------------------------------------------------------------------------- /src/routes/projects/mobile-filter-open.tsx: -------------------------------------------------------------------------------- 1 | import { type QRL, component$, $, useSignal } from "@builder.io/qwik"; 2 | import FunnelIcon from "~/media/icons/funnel-icon.svg?jsx"; 3 | 4 | type Props = { 5 | display: boolean; 6 | onClick$: QRL<() => void>; 7 | }; 8 | 9 | export default component$((props) => { 10 | const loading = useSignal(false); 11 | 12 | const handleButtonClick = $(async () => { 13 | loading.value = true; 14 | await props.onClick$(); 15 | loading.value = false; 16 | }); 17 | 18 | return ( 19 |
25 |
26 | 43 |
44 |
45 | ); 46 | }); 47 | -------------------------------------------------------------------------------- /src/routes/projects/page-nav.tsx: -------------------------------------------------------------------------------- 1 | import { component$, $, useOnDocument } from "@builder.io/qwik"; 2 | 3 | import PageNextButton from "./page-next-button"; 4 | import PagePrevButton from "./page-prev-button"; 5 | import PageNumberButton from "./page-number-button"; 6 | import { isBrowser } from "@builder.io/qwik/build"; 7 | 8 | type PageNavProps = { 9 | currentPage: any; 10 | itemsPerPage: number; 11 | totalItems: number; 12 | }; 13 | 14 | function calculatePageRange(currentPage: number, totalPage: number) { 15 | let startPage = Math.max(currentPage - 1, 2); 16 | let endPage = Math.min(currentPage + 1, totalPage - 1); 17 | 18 | if (currentPage - 1 < 2) { 19 | endPage = 4; 20 | } else if (totalPage - currentPage < 2) { 21 | startPage = totalPage - 3; 22 | } 23 | 24 | return { startPage, endPage }; 25 | } 26 | 27 | function generatePageNumbers(totalPage: number, currentPageValue: number) { 28 | if (totalPage <= 8) { 29 | return Array.from({ length: totalPage }, (_, i) => i + 1); 30 | } 31 | 32 | const pages = [1]; 33 | 34 | const { startPage, endPage } = calculatePageRange( 35 | currentPageValue, 36 | totalPage, 37 | ); 38 | 39 | if (startPage > 2) pages.push(-1); 40 | for (let i = startPage; i <= endPage; i++) pages.push(i); 41 | if (endPage < totalPage - 1) pages.push(-1); 42 | pages.push(totalPage); 43 | 44 | return pages; 45 | } 46 | 47 | export const PageNav = component$( 48 | ({ currentPage, itemsPerPage, totalItems }) => { 49 | const totalPage = Math.ceil(totalItems / itemsPerPage); 50 | 51 | const updateQueryParameter = $(() => { 52 | // Workaround for the issue that the query parameters are not extracted in useLocation 53 | const queryParameters = new URLSearchParams(document.location.search); 54 | queryParameters.set("page", currentPage.value.toString()); 55 | window.history.replaceState({}, "", `?${queryParameters}`); 56 | }); 57 | 58 | const initQueryParameter = $(() => { 59 | // Workaround for the issue that the query parameters are not extracted in useLocation 60 | const queryParameters = new URLSearchParams(document.location.search); 61 | const page = queryParameters.get("page"); 62 | if (page === null) { 63 | currentPage.value = 1; 64 | updateQueryParameter(); 65 | return; 66 | } 67 | 68 | const pageNumber = parseInt(page); 69 | 70 | if (!isNaN(pageNumber) && pageNumber >= 1 && pageNumber <= totalPage) { 71 | currentPage.value = pageNumber; 72 | } else { 73 | currentPage.value = 1; 74 | } 75 | updateQueryParameter(); 76 | }); 77 | 78 | useOnDocument("DOMContentLoaded", initQueryParameter); 79 | 80 | const handleNextPage = $(() => { 81 | if (currentPage.value * itemsPerPage >= totalItems) { 82 | return; 83 | } 84 | currentPage.value++; 85 | window.scrollTo({ top: 0, behavior: "smooth" }); 86 | updateQueryParameter(); 87 | }); 88 | 89 | const handlePrevPage = $(() => { 90 | if (currentPage.value === 1) return; 91 | currentPage.value--; 92 | window.scrollTo({ top: 0, behavior: "smooth" }); 93 | updateQueryParameter(); 94 | }); 95 | 96 | const handleDirect = $((target: number) => { 97 | currentPage.value = target; 98 | window.scrollTo({ top: 0, behavior: "smooth" }); 99 | updateQueryParameter(); 100 | }); 101 | 102 | const generatePageList = () => { 103 | const pages = generatePageNumbers(totalPage, currentPage.value); 104 | isBrowser && updateQueryParameter(); 105 | return pages.map((page, index) => ( 106 | 112 | )); 113 | }; 114 | 115 | return ( 116 |
117 | 121 | 122 | = totalItems} 125 | /> 126 |
127 | ); 128 | }, 129 | ); 130 | -------------------------------------------------------------------------------- /src/routes/projects/page-next-button.tsx: -------------------------------------------------------------------------------- 1 | import { component$, $, useSignal, type QRL } from "@builder.io/qwik"; 2 | import ArrowRightIcon from "~/media/icons/arrow-right-icon.svg?jsx"; 3 | 4 | type PageNavProps = { 5 | disabled: boolean; 6 | onClick$: QRL<() => void>; 7 | }; 8 | 9 | export default component$((props) => { 10 | const loading = useSignal(false); 11 | 12 | const handlePageChange = $(async () => { 13 | loading.value = true; 14 | await props.onClick$(); 15 | loading.value = false; 16 | }); 17 | 18 | return ( 19 | 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /src/routes/projects/page-number-button.tsx: -------------------------------------------------------------------------------- 1 | import { $, component$, useSignal, type QRL } from "@builder.io/qwik"; 2 | 3 | type Props = { 4 | key: number; 5 | target: number; 6 | current: number; 7 | onClick$: QRL<(target: number) => void>; 8 | }; 9 | 10 | export default component$((props) => { 11 | const loading = useSignal(false); 12 | 13 | const handleButtonClick = $(async () => { 14 | loading.value = true; 15 | await props.onClick$(props.target); 16 | loading.value = false; 17 | }); 18 | 19 | return ( 20 | 49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /src/routes/projects/page-prev-button.tsx: -------------------------------------------------------------------------------- 1 | import { component$, $, useSignal, type QRL } from "@builder.io/qwik"; 2 | import ArrowLeftIcon from "~/media/icons/arrow-left-icon.svg?jsx"; 3 | 4 | type PageNavProps = { 5 | disabled: boolean; 6 | onClick$: QRL<() => void>; 7 | }; 8 | 9 | export default component$((props) => { 10 | const loading = useSignal(false); 11 | 12 | const handlePageChange = $(async () => { 13 | loading.value = true; 14 | await props.onClick$(); 15 | loading.value = false; 16 | }); 17 | 18 | return ( 19 | 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /src/routes/projects/repo-block.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import ArrowRightIcon from "~/media/icons/arrow-right-icon.svg?jsx"; 3 | import LabelButton from "./label"; 4 | import Link from "~/components/link"; 5 | 6 | export const RepoBlock = component$( 7 | ({ 8 | name, 9 | mainCopyrightOwner, 10 | mainCopyrightOwnerLogo, 11 | shortDescription, 12 | features = [], 13 | dependsOn = [], 14 | techStacks = [], 15 | id, 16 | }: { 17 | name: string; 18 | repoOwner?: string; 19 | mainCopyrightOwner: string; 20 | mainCopyrightOwnerLogo?: string; 21 | shortDescription: string; 22 | features?: string[]; 23 | dependsOn?: { name: string }[]; 24 | techStacks?: { name: string }[]; 25 | id: number; 26 | }) => { 27 | const mainCopyrightOwnerString = $localize`提供單位:`; 28 | return ( 29 |
30 |
31 |
32 |
{name}
33 |
34 | {mainCopyrightOwnerLogo ? ( 35 | {mainCopyrightOwner} 42 | ) : ( 43 | mainCopyrightOwnerString + mainCopyrightOwner 44 | )} 45 |
46 |
47 |
{shortDescription}
48 |
49 |
50 |
51 |
{$localize`功能類型`}
52 |
53 | {features.map((feature) => ( 54 | {feature} 55 | ))} 56 |
57 |
58 |
59 |
{$localize`使用技術`}
60 |
61 | {dependsOn.map((depend: { name: string }) => ( 62 | {depend.name} 63 | ))} 64 | {techStacks.map((tech: { name: string }) => ( 65 | {tech.name} 66 | ))} 67 |
68 |
69 |
70 |
71 | 72 |
78 |
79 |
{$localize`更多細節`}
80 |
81 | 82 |
83 |
84 |
85 | 86 |
87 | ); 88 | }, 89 | ); 90 | -------------------------------------------------------------------------------- /src/routes/projects/repo-list.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import projectData from "~/data/projects.json"; 3 | import { RepoBlock } from "~/routes/projects/repo-block"; 4 | 5 | type Props = { 6 | projectsID: number[]; 7 | }; 8 | 9 | export default component$((props) => { 10 | return ( 11 | <> 12 | {props.projectsID.map((index) => { 13 | const project = projectData[index]; 14 | const projectName = 15 | project.description["zh-Hant"].localisedName || project.name; 16 | const mainCopyrightOwner = project.legal.mainCopyrightOwner; 17 | const repoOwner = project.legal.repoOwner.split(" ")[0]; 18 | const projectDescription = 19 | project.description["zh-Hant"].shortDescription; 20 | const projectFeatures = project.description["zh-Hant"].features; 21 | const mainCopyrightOwnerLogo = project.tw.mainCopyrightOwnerLogo; 22 | return ( 23 | 35 | ); 36 | })} 37 | 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /src/routes/service-worker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * WHAT IS THIS FILE? 3 | * 4 | * The service-worker.ts file is used to have state of the art prefetching. 5 | * https://qwik.builder.io/qwikcity/prefetching/overview/ 6 | * 7 | * Qwik uses a service worker to speed up your site and reduce latency, ie, not used in the traditional way of offline. 8 | * You can also use this file to add more functionality that runs in the service worker. 9 | */ 10 | import { setupServiceWorker } from "@builder.io/qwik-city/service-worker"; 11 | 12 | setupServiceWorker(); 13 | 14 | addEventListener("install", () => { 15 | self.skipWaiting(); 16 | }); 17 | 18 | addEventListener("activate", () => { 19 | self.clients.claim(); 20 | }); 21 | 22 | declare const self: ServiceWorkerGlobalScope; 23 | -------------------------------------------------------------------------------- /src/routes/submit/index.tsx: -------------------------------------------------------------------------------- 1 | import { component$ } from "@builder.io/qwik"; 2 | import Section from "~/components/section"; 3 | import Form from "./form"; 4 | 5 | export default component$(() => { 6 | return ( 7 | <> 8 |
9 |

{$localize`提供公共程式`}

10 |
11 | {$localize`公共程式是各政府機關分享開放的系統程式碼。若貴機關也有可露出於本平臺的公共程式,歡迎填寫以下表單與我們聯絡。`} 12 |
13 |
14 |
15 |
16 | 22 |
23 | 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /src/routes/submit/modular-forms.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | email, 3 | type Input, 4 | maxLength, 5 | minLength, 6 | object, 7 | string, 8 | url, 9 | optional, 10 | } from "valibot"; 11 | import "@angular/localize/init"; 12 | 13 | export const SubmitSchema = object({ 14 | projectName: string([minLength(1, $localize`需要填寫公共程式名稱`)]), 15 | projectDescription: string([ 16 | minLength(1, $localize`需要填寫公共程式描述`), 17 | maxLength(150, $localize`描述須小於 150 字`), 18 | ]), 19 | projectUrl: string([ 20 | minLength(1, $localize`專案網址是必填欄位`), 21 | url($localize`無效的網址`), 22 | ]), 23 | demoUrl: optional(string()), 24 | provider: string([minLength(1, $localize`提供單位是必填欄位`)]), 25 | contactName: string([minLength(1, $localize`聯絡窗口是必填欄位`)]), 26 | contactPhone: string([minLength(1, $localize`聯絡電話是必填欄位`)]), 27 | contactEmail: string([ 28 | minLength(1, $localize`聯絡 email 是必填欄位`), 29 | email($localize`無效的 email`), 30 | ]), 31 | }); 32 | 33 | export type SubmitForm = Input; 34 | -------------------------------------------------------------------------------- /src/routes/submit/text-input.tsx: -------------------------------------------------------------------------------- 1 | import { type QRL, component$ } from "@builder.io/qwik"; 2 | import QuestionMark from "~/media/icons/question-mark.svg?jsx"; 3 | import ExclamationCircle from "~/media/icons/exclamation-circle.svg?jsx"; 4 | 5 | type TextInputProps = { 6 | id?: string; 7 | name: string; 8 | type: "text" | "email" | "tel" | "password" | "url" | "date"; 9 | label?: string; 10 | placeholder?: string; 11 | value: string | undefined; 12 | error: string; 13 | required?: boolean; 14 | ref: QRL<(element: HTMLInputElement) => void>; 15 | onInput$: (event: Event, element: HTMLInputElement) => void; 16 | onChange$: (event: Event, element: HTMLInputElement) => void; 17 | onBlur$: (event: Event, element: HTMLInputElement) => void; 18 | tooltips?: string; 19 | }; 20 | 21 | export default component$(({ label, error, ...props }) => { 22 | const { id, name, required } = props; 23 | return ( 24 |
25 | 43 |
44 | 53 |
59 | 60 |
61 |
62 | {error && ( 63 | 67 | {error} 68 | 69 | )} 70 |
71 | ); 72 | }); 73 | -------------------------------------------------------------------------------- /src/routes/submit/textarea-input.tsx: -------------------------------------------------------------------------------- 1 | import { type QRL, component$ } from "@builder.io/qwik"; 2 | import QuestionMark from "~/media/icons/question-mark.svg?jsx"; 3 | import ExclamationCircle from "~/media/icons/exclamation-circle.svg?jsx"; 4 | 5 | type TextareaInputProps = { 6 | id?: string; 7 | name: string; 8 | label?: string; 9 | placeholder?: string; 10 | value: string | undefined; 11 | error: string; 12 | required?: boolean; 13 | ref: QRL<(element: HTMLTextAreaElement) => void>; 14 | onInput$: (event: Event, element: HTMLTextAreaElement) => void; 15 | onChange$: (event: Event, element: HTMLTextAreaElement) => void; 16 | onBlur$: (event: Event, element: HTMLTextAreaElement) => void; 17 | tooltips?: string; 18 | }; 19 | 20 | export default component$(({ label, error, ...props }) => { 21 | const { id, name, required } = props; 22 | return ( 23 |
24 | 42 |
43 |