diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5cb4553 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: Chromium +SortIncludes: false diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d3afd9b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.DS_Store +.vscode +.idea +.rollup.cache +.vercel +tsconfig.tsbuildinfo + +node_modules +build +dist diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..e19bebf --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,122 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:import/recommended", + "plugin:import/typescript", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/strict" + ], + "parserOptions": { + "ecmaVersion": "latest", + "project": "tsconfig.json" + }, + "plugins": [ + "@typescript-eslint", + "import", + "react", + "jsx-a11y", + "react-refresh", + "no-array-concat" + ], + "ignorePatterns": [ + "node_modules", + "boost", + "librime", + "schema", + "public", + "build", + "dist" + ], + "settings": { + "import/core-modules": ["bun", "react", "react-dom"] + }, + "rules": { + "default-case-last": "error", + "eqeqeq": "error", + "func-style": ["error", "declaration"], + "grouped-accessor-pairs": ["error", "getBeforeSet"], + "logical-assignment-operators": "error", + "no-array-concat/no-array-concat": "error", + "no-constant-binary-expression": "error", + "no-control-regex": "off", + "no-irregular-whitespace": ["error", { + "skipStrings": true, + "skipComments": true, + "skipRegExps": true, + "skipTemplates": true, + "skipJSXText": true + }], + "no-negated-condition": "warn", + "no-self-compare": "error", + "no-template-curly-in-string": "warn", + "no-undef-init": "error", + "no-unmodified-loop-condition": "warn", + "no-unneeded-ternary": "error", + "no-unreachable-loop": "warn", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-return": "error", + "no-var": "error", + "operator-assignment": "error", + "prefer-const": "error", + "prefer-exponentiation-operator": "error", + "prefer-object-spread": "error", + "prefer-regex-literals": "error", + "prefer-rest-params": "error", + "prefer-template": "warn", + + "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/default-param-last": "error", + "@typescript-eslint/no-confusing-void-expression": "off", + "@typescript-eslint/no-loop-func": "warn", + "@typescript-eslint/no-misused-promises": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unnecessary-qualifier": "error", + "@typescript-eslint/no-useless-empty-export": "error", + "@typescript-eslint/parameter-properties": ["error", { + "prefer": "parameter-property" + }], + "@typescript-eslint/prefer-nullish-coalescing": "off", + "@typescript-eslint/prefer-regexp-exec": "error", + "@typescript-eslint/prefer-string-starts-ends-with": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/restrict-plus-operands": ["error", { + "allowAny": true, + "allowBoolean": true, + "allowNumberAndString": true + }], + "@typescript-eslint/restrict-template-expressions": ["error", { + "allowAny": true, + "allowBoolean": true, + "allowNumber": true, + "allowNever": true + }], + + "import/consistent-type-specifier-style": ["error", "prefer-top-level"], + "import/extensions": "error", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-absolute-path": "error", + "import/no-anonymous-default-export": ["warn", { "allowCallExpression": false }], + "import/no-extraneous-dependencies": ["error", { "includeInternal": true, "includeTypes": true }], + "import/no-named-default": "error", + "import/no-self-import": "error", + "import/no-useless-path-segments": ["error", { "noUselessIndex": true }], + "import/order": ["error", { + "groups": ["builtin", "external", "internal", ["parent", "sibling", "index"], "type", "unknown", "object"], + "newlines-between": "always", + "alphabetize": { "order": "asc", "orderImportKind": "asc", "caseInsensitive": true }, + "warnOnUnassignedImports": true + }], + + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + + "react-refresh/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c26c29f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,89 @@ +name: Build +on: + workflow_call: + pull_request: +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-13, macos-14, windows-latest] + steps: + - name: Checkout Latest Commit + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install Ubuntu Dependencies + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt update + sudo apt upgrade -y + sudo apt install -y \ + ninja-build \ + libboost-dev \ + libboost-regex-dev \ + libyaml-cpp-dev \ + libleveldb-dev \ + libmarisa-dev \ + libopencc-dev + echo "CC=/usr/bin/clang" >> $GITHUB_ENV + echo "CXX=/usr/bin/clang++" >> $GITHUB_ENV + - name: Install macOS Dependencies + if: ${{ startsWith(matrix.os, 'macos') }} + run: | + brew install ninja + - name: Install Windows Dependencies + if: ${{ matrix.os == 'windows-latest' }} + run: | + choco upgrade -y llvm + pip install ninja + echo "$env:ProgramFiles\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - name: Setup Emscripten SDK + uses: mymindstorm/setup-emsdk@v14 + - name: Install Package Dependencies + run: | + bun i + - name: Prepare Boost + run: | + bun run boost + - name: Build Native + run: | + bun run native + - name: Build Schema + run: | + bun run schema + - name: Build Library + run: | + bun run lib + - name: Build WebAssembly + run: | + bun run wasm + - name: Build App + run: | + bun run build + docker: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout Latest Commit + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Build App + run: | + docker build -t typeduck-web --no-cache . + - name: Compress Files + run: | + mv dist TypeDuck-Web + tar -cvf TypeDuck-Web.tar TypeDuck-Web + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: TypeDuck-Web + path: TypeDuck-Web.tar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..607bdd8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Release +on: + push: + branches: + - main +jobs: + build: + uses: ./.github/workflows/build.yml + release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download Artifact + uses: actions/download-artifact@v4 + with: + name: TypeDuck-Web + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + run: | + tar -xvf TypeDuck-Web.tar + zip -r TypeDuck-Web.zip TypeDuck-Web + gh release upload latest TypeDuck-Web.zip --clobber diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36878c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.DS_Store +.vscode +!.vscode/extensions.json +.idea +.rollup.cache +.vercel +tsconfig.tsbuildinfo + +node_modules +boost-*.tar.xz +boost +build +public +dist diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0591590 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "librime"] + path = librime + url = https://github.com/TypeDuck-HK/librime.git +[submodule "schema"] + path = schema + url = https://github.com/TypeDuck-HK/schema.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9e9ce3d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM oven/bun as builder +FROM emscripten/emsdk +FROM nginx:stable-alpine-slim + +ARG ENABLE_LOGGING=ON +ENV ENABLE_LOGGING ${ENABLE_LOGGING} + +RUN apt update +RUN apt upgrade -y +RUN apt install -y \ + git \ + cmake \ + ninja-build \ + libboost-dev \ + libboost-regex-dev \ + libyaml-cpp-dev \ + libleveldb-dev \ + libmarisa-dev \ + libopencc-dev + +COPY / /TypeDuck-Web +WORKDIR /TypeDuck-Web + +RUN bun i +RUN bun run boost +RUN bun run native +RUN bun run schema +RUN bun run lib +RUN bun run wasm +RUN bun run build + +COPY --from=builder /TypeDuck-Web/dist /usr/share/nginx/html diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..364fef3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024, TypeDuck Team + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..2d94fcb Binary files /dev/null and b/bun.lockb differ diff --git a/dprint.jsonc b/dprint.jsonc new file mode 100644 index 0000000..1a00b37 --- /dev/null +++ b/dprint.jsonc @@ -0,0 +1,57 @@ +// Adopt from https://github.com/microsoft/TypeScript with minor changes +{ + "indentWidth": 4, + "lineWidth": 1000, + "newLineKind": "crlf", + "useTabs": true, + "typescript": { + "semiColons": "prefer", + "quoteStyle": "preferDouble", + "quoteProps": "consistent", + "useBraces": "whenNotSingleLine", + "bracePosition": "sameLineUnlessHanging", + "singleBodyPosition": "sameLine", + "nextControlFlowPosition": "nextLine", + "trailingCommas": "onlyMultiLine", + "operatorPosition": "nextLine", + "preferHanging": false, + + "arrowFunction.useParentheses": "preferNone", + "jsx.bracketPosition": "sameLine", + "jsx.forceNewLinesSurroundingContent": false, + "jsx.multiLineParens": "never", + "functionExpression.spaceAfterFunctionKeyword": true, + "constructorType.spaceAfterNewKeyword": true, + "constructSignature.spaceAfterNewKeyword": true, + + // Let ESLint handle this. + "module.sortImportDeclarations": "maintain", + "module.sortExportDeclarations": "maintain", + "exportDeclaration.sortNamedExports": "maintain", + "importDeclaration.sortNamedImports": "maintain" + }, + "json": { + "trailingCommas": "never" + }, + "markdown": {}, + "prettier": { + "associations": [ + "**/*.{html,htm,xhtml,css,scss,sass,less,graphql,graphqls,gql,yaml,yml}" + ] + }, + "excludes": [ + "node_modules", + "boost", + "librime", + "schema", + "public", + "build", + "dist" + ], + "plugins": [ + "https://plugins.dprint.dev/typescript-0.89.3.wasm", + "https://plugins.dprint.dev/json-0.19.2.wasm", + "https://plugins.dprint.dev/markdown-0.16.4.wasm", + "https://plugins.dprint.dev/prettier-0.39.0.json@896b70f29ef8213c1b0ba81a93cee9c2d4f39ac2194040313cd433906db7bc7c" + ] +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..1f23d40 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + +
+ + +{name} | +
+ {value.split(",").map((line, i) => {line} )}
+ |
+
---|
{name} | +{value} | +
---|
}`]: Dispatch