This commit is contained in:
dream-num
2022-09-29 20:12:19 +08:00
commit a6b429a781
1862 changed files with 197920 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
+9
View File
@@ -0,0 +1,9 @@
.eslintrc.js
vite.config.ts
**/*.test.js
dist
lib
node_modules
package.json
tsconfig.json
jest.config.js
+113
View File
@@ -0,0 +1,113 @@
module.exports = {
// env: {
// browser: true,
// es2021: true,
// node: true,
// jest: true,
// },
globals: {
page: true,
browser: true,
context: true,
jestPuppeteer: true,
},
// parser: '@typescript-eslint/parser',
// extends: ['airbnb-typescript', 'prettier', 'eslint:recommended', 'plugin:import/recommended', 'plugin:import/typescript'],
extends: ['airbnb-base', 'airbnb-typescript/base', 'prettier'],
env: {
browser: true,
es2021: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
ecmaVersion: 12,
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./packages/*/tsconfig.json'],
},
plugins: ['@typescript-eslint', 'prettier', 'import', 'import-newlines', 'unused-imports'],
rules: {
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'import/prefer-default-export': 'off',
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
// turn on errors for missing imports
'import/no-unresolved': [2, { ignore: ['\\.less$', '^@'] }],
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-shadow': 'off',
'import/no-cycle': 'off',
'no-param-reassign': 'off',
'no-bitwise': 'off',
'default-case': 'off',
'class-methods-use-this': 'off',
'consistent-return': 'off',
'no-underscore-dangle': 'off',
'no-restricted-syntax': 'off',
'max-classes-per-file': 'off',
'prefer-destructuring': 'off',
'no-plusplus': 'off',
'no-return-assign': 'off',
'no-continue': 'off',
'no-loop-func': 'off',
'@typescript-eslint/no-loop-func': 'off',
'guard-for-in': 'off',
'no-prototype-builtins': 'off',
'no-lonely-if': 'off',
'prefer-const': 'off',
radix: 'off',
'no-nested-ternary': 'off',
'no-new': 'off',
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'no-console': 'off',
'no-multi-assign': 'off',
'no-restricted-properties': 'off',
'no-control-regex': 'off',
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
'no-await-in-loop': 'off',
'@typescript-eslint/explicit-member-accessibility': [
'error',
{
accessibility: 'no-public',
},
],
'@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'as', objectLiteralTypeAssertions: 'allow-as-parameter' }],
'spaced-comment': 'off',
eqeqeq: ['error', 'always', { null: 'ignore' }],
// eslint-plugin-unused-imports
'@typescript-eslint/no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'error',
'prefer-regex-literals': 'off',
'@typescript-eslint/default-param-last': 'off',
'grouped-accessor-pairs': 'off',
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
// always try to resolve config under `<root>@config`
// directory even it doesn't contain any source code,
// like `@config/unist`
alwaysTryTypes: true,
// Choose from one of the "project" configs below or omit
// to use <root>/tsconfig.json by default
// use <root>/path/to/folder/tsconfig.json
project: './tsconfig.json',
},
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
};
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
# Univer Contributing Guide
## Tool
- vite
- Typescript/tsx
- Typescript references
- pnpm workspace
## Project Introduction
For multi-package projects managed by lerna, there are plugins and cores in the packages directory, and examples are in the examples directory
## Develop
### Install dependencies
Node.js >= 14.19
```bash
git clone http://github.com/dream-num/univer
cd univer
npm i -g pnpm # MacOS : sudo npm i -g pnpm
npx playwright install
pnmp install
npm run dev
```
### Command
Execute a command in a subpackage
```bash
pnpm run --filter [package name] [command]
```
For example, start the `dev` development mode of the `packages/sheets-plugin-sort` project
```bash
pnpm run --filter @univer/sheets-plugin-sort dev
```
### Clean
If you encounter any npm installation problems, please try one-click reinstallation of dependencies first
```bash
npm run clean
```
### Plug-in development
1. Quickly generate plug-in template directory
```bash
npm run cli
```
2. For example, select the `plugin-temp` template and enter the plugin name `filter` to generate the `packages/sheets-plugin-filter` plugin.
3. Execute the following command to start the plug-in development mode
```bash
pnpm run --filter @univer/sheets-plugin-filter dev
```
4. There will be an additional `filter` plugin button in the toolbar on the interface
## Code comment
English is preferred, if the comment is longer, you can add bilingual
```js
/*
en: get result
zh: 得出结果
*/
function(){
}
```
## Update a dependency package
```bash
pnpm update
```
+98
View File
@@ -0,0 +1,98 @@
#ide
*.DS_Store
.idea
# .vscode
#npm
package-lock.json
yarn.lock
pnpm-lock.yaml
dist
lib
docs/.vuepress/dist
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Runtime config
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL History
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
#lerna-changelog
.changelog
#editor workspace file
.idea/
.history
# examples
screenshot
univer-custom-build
universheet-custom-build
temp
etc
# api extractor, api documenter
docs/input
docs/markdown
doc
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# npm run lint
+1
View File
@@ -0,0 +1 @@
auto-install-peers=true
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
singleQuote: true,
trailingComma: 'es5',
tabWidth: 4,
semi: true,
printWidth: 180,
};
+25
View File
@@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"name": "vscode-jest-tests",
"request": "launch",
"args": [
"--runInBand",
"--watchAll=false"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"program": "${workspaceFolder}/node_modules/.bin/jest",
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
}
}
]
}
+64
View File
@@ -0,0 +1,64 @@
{
"prettier.eslintIntegration": true,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"commentTranslate.targetLanguage": "zh-CN",
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
},
"commentTranslate.hover.enabled": true,
"commentTranslate.source": "Google",
"cSpell.words": [
"Bandings",
"Catmull",
"clazz",
"codelf",
"compositionend",
"compositionstart",
"compositionupdate",
"CRDT",
"csstype",
"customd",
"Datas",
"esbuild",
"evented",
"execa",
"FONTFACE",
"fract",
"gainsboro",
"Gridlines",
"Hanalei",
"hdmx",
"Heiti",
"Hiragino",
"ilvl",
"Kaiti",
"Lerp",
"Liti",
"universheet",
"mengshukeji",
"Overlines",
"Pacifico",
"pnmp",
"ponyfill",
"preact",
"PWDEBUG",
"Quan",
"Sider",
"SUMIF",
"Tahoma",
"univer",
"universheet",
"Unmerge",
"Verdana",
"viewports",
"Xingkai",
"Xinwei"
],
"javascript.preferences.importModuleSpecifier": "relative",
"typescript.preferences.importModuleSpecifier": "relative"
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020-present, DreamNum
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:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
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.
+133
View File
@@ -0,0 +1,133 @@
# Univer
##
简体中文 | [English](./README.md)
## 介绍
🚀Univer,原 [Luckysheet 2.0](https://github.com/dream-num/Luckysheet) 升级。
> ⚠️该项目仍在开发中,仅用于测试和学习,不得用于生产
## 开发
### 环境
[Node.js](https://nodejs.org/en/) Version >= 14.19
### 安装
```
git clone http://github.com/dream-num/univer
cd univer
npm i -g pnpm # MacOS : sudo npm i -g pnpm
npx playwright install
pnmp i
```
### 开发
```
npm run dev
```
更多开发教程请参考 [贡献指南](./.github/contributing-zh.md)。
## 问题反馈
请到 [Issues](http://github.com/dream-num/univer/issues) 提交问题。
## 开发计划
### Sheets
- 基础能力
- [x] 核心数据结构
- [x] 渲染
- [x] 界面
- [x] 单元格编辑
- sheet操作
- [x] sheet删除
- [x] sheet复制
- [x] sheet排序
- [x] sheet重命名
- [x] sheet更改颜色
- [x] sheet隐藏
- [x] sheet取消隐藏
- [x] sheet向左移
- [x] sheet向右移
- 右击菜单
- [x] 复制
- [x] 粘贴
- [x] 插入行
- [x] 插入列
- [x] 删除选中行
- [x] 删除选中列
- [x] 删除单元格
- [x] 清除内容
- 工具栏
- [x] 撤销
- [x] 重做
- [ ] 格式刷
- [ ] 货币格式
- [ ] 百分比格式
- [ ] 减少小数位数
- [ ] 增加小数位数
- [ ] 更多格式
- [x] 字体
- [x] 字体大小
- [x] 加粗
- [x] 斜体
- [x] 删除线
- [x] 下划线
- [x] 文本颜色
- [x] 单元格颜色
- [x] 边框
- [x] 合并单元格
- [x] 水平对齐
- [x] 垂直对齐
- [x] 文本换行
- [x] 文本旋转
- 计数栏
- [x] 计数
- [x] 缩放
- 插件
- [x] 剪切板
- [ ] 单元格格式
- [ ] 公式
- [ ] 图片
- [ ] 冻结
- [ ] 链接
- [ ] 批注
- [ ] 数据透视表
- [ ] 图表
- [ ] 排序
- [ ] 筛选
- [ ] 条件格式
- [ ] 交替颜色
- [ ] 数据验证
- [ ] 分列
- [ ] 截图
- [ ] 查找
- [ ] 替换
- [ ] 工作表保护
- [ ] 打印
- [ ] 导入导出
- [ ] 协同
### Docs
- [x] 渲染
- [ ] 界面
- [ ] 待定
### Slides
- [ ] 渲染
- [ ] 界面
- [ ] 待定
+131
View File
@@ -0,0 +1,131 @@
# Univer
English| [简体中文](./README-zh.md)
## Introduction
Univer,[Luckysheet 2.0](https://github.com/dream-num/luckysheet) upgrade version.
> ⚠️ This project is still in development, only for testing and learning, not for production
## Development
### Requirements
[Node.js](https://nodejs.org/en/) Version >= 14.19
### Installation
````
git clone http://github.com/dream-num/univer
cd univer
npm i -g pnpm # MacOS : sudo npm i -g pnpm
npx playwright install
pnmp i
````
### Development
````
npm run dev
````
For more development tutorials, please refer to [Contribution Guide](./.github/contributing.md).
## Issues
Please file an issue at [Issues](http://github.com/dream-num/univer/issues).
## Development Plan
### Sheets
- Basic ability
- [x] core data structures
- [x] render
- [x] UI
- [x] cell editing
- Sheet operation
- [x] sheet delete
- [x] sheet copy
- [x] sheet sort
- [x] sheet rename
- [x] sheet change color
- [x] sheet hide
- [x] sheet unhide
- [x] sheet move left
- [x] sheet moves right
- Right click menu
- [x] copy
- [x] paste
- [x] insert row
- [x] insert column
- [x] delete selected rows
- [x] delete selected columns
- [x] delete cell
- [x] clear content
- Toolbar
- [x] undo
- [x] redo
- [ ] formatter
- [ ] currency format
- [ ] percent format
- [ ] Decrease decimal places
- [ ] increase the number of decimal places
- [ ] more formats
- [x] font family
- [x] font size
- [x] bold
- [x] italic
- [x] strikethrough
- [x] underscore
- [x] text color
- [x] fill color
- [x] border
- [x] merge cells
- [x] horizontal alignment
- [x] vertical alignment
- [x] text wrap
- [x] text rotation
- Count bar
- [x] count
- [x] zoom
- Plugins
- [x] clipboard
- [ ] format
- [ ] formula
- [ ] image
- [ ] Frozen
- [ ] link
- [ ] comment
- [ ] pivot table
- [ ] chart
- [ ] sort
- [ ] filter
- [ ] conditional format
- [ ] alternating colors
- [ ] data validation
- [ ] split column
- [ ] screenshot
- [ ] find
- [ ] replace
- [ ] protection
- [ ] print
- [ ] import and export
- [ ] collaboration
### Docs
- [x] render
- [ ] UI
- [ ] Pending
### Slides
- [ ] render
- [ ] UI
- [ ] Pending
+380
View File
@@ -0,0 +1,380 @@
/**
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
/**
* Optionally specifies another JSON config file that this file extends from. This provides a way for
* standard settings to be shared across multiple projects.
*
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
* resolved using NodeJS require().
*
* SUPPORTED TOKENS: none
* DEFAULT VALUE: ""
*/
// "extends": "./shared/api-extractor-base.json"
// "extends": "my-package/include/api-extractor-base.json"
/**
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
*
* The path is resolved relative to the folder of the config file that contains the setting.
*
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
* will be reported.
*
* SUPPORTED TOKENS: <lookup>
* DEFAULT VALUE: "<lookup>"
*/
// "projectFolder": "..",
/**
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
* analyzes the symbols exported by this module.
*
* The file extension must be ".d.ts" and not ".ts".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
*/
"mainEntryPointFilePath": "<projectFolder>/lib/src/index.d.ts",
/**
* A list of NPM package names whose exports should be treated as part of this package.
*
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
* imports library2. To avoid this, we can specify:
*
* "bundledPackages": [ "library2" ],
*
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
* local files for library1.
*/
"bundledPackages": [],
/**
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
*/
"compiler": {
/**
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* Note: This setting will be ignored if "overrideTsconfig" is used.
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
*/
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
/**
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
* The object must conform to the TypeScript tsconfig schema:
*
* http://json.schemastore.org/tsconfig
*
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
*
* DEFAULT VALUE: no overrideTsconfig section
*/
// "overrideTsconfig": {
// . . .
// }
/**
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
*
* DEFAULT VALUE: false
*/
// "skipLibCheck": true,
},
/**
* Configures how the API report file (*.api.md) will be generated.
*/
"apiReport": {
/**
* (REQUIRED) Whether to generate an API report.
*/
"enabled": true,
/**
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
* a full file path.
*
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
*
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
*/
// "reportFileName": "<unscopedPackageName>.api.md",
/**
* Specifies the folder where the API report file is written. The file name portion is determined by
* the "reportFileName" setting.
*
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
* e.g. for an API review.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/etc/"
*/
"reportFolder": "<projectFolder>/etc/"
/**
* Specifies the folder where the temporary report file is written. The file name portion is determined by
* the "reportFileName" setting.
*
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
* If they are different, a production build will fail.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/"
*/
// "reportTempFolder": "<projectFolder>/temp/"
},
/**
* Configures how the doc model file (*.api.json) will be generated.
*/
"docModel": {
/**
* (REQUIRED) Whether to generate a doc model file.
*/
"enabled": true
/**
* The output path for the doc model file. The file extension should be ".api.json".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
*/
// "apiJsonFilePath": "<projectFolder>/temp/<unscopedPackageName>.api.json"
},
/**
* Configures how the .d.ts rollup file will be generated.
*/
"dtsRollup": {
/**
* (REQUIRED) Whether to generate the .d.ts rollup file.
*/
"enabled": true
/**
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
* This file will include all declarations that are exported by the main entry point.
*
* If the path is an empty string, then this file will not be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
*/
// "untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release.
* This file will include only declarations that are marked as "@public", "@beta", or "@alpha".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "alphaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-alpha.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
* This file will include only declarations that are marked as "@public" or "@beta".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
* This file will include only declarations that are marked as "@public".
*
* If the path is an empty string, then this file will not be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
/**
* When a declaration is trimmed, by default it will be replaced by a code comment such as
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
* declaration completely.
*
* DEFAULT VALUE: false
*/
// "omitTrimmingComments": true
},
/**
* Configures how the tsdoc-metadata.json file will be generated.
*/
"tsdocMetadata": {
/**
* Whether to generate the tsdoc-metadata.json file.
*
* DEFAULT VALUE: true
*/
// "enabled": true,
/**
* Specifies where the TSDoc metadata file should be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
* falls back to "tsdoc-metadata.json" in the package folder.
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<lookup>"
*/
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
},
/**
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
* To use the OS's default newline kind, specify "os".
*
* DEFAULT VALUE: "crlf"
*/
// "newlineKind": "crlf",
/**
* Configures how API Extractor reports error and warning messages produced during analysis.
*
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
*/
"messages": {
/**
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
* the input .d.ts files.
*
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
*
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
*/
"compilerMessageReporting": {
/**
* Configures the default routing for messages that don't match an explicit rule in this table.
*/
"default": {
/**
* Specifies whether the message should be written to the the tool's output log. Note that
* the "addToApiReportFile" property may supersede this option.
*
* Possible values: "error", "warning", "none"
*
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
* the "--local" option), the warning is displayed but the build will not fail.
*
* DEFAULT VALUE: "warning"
*/
"logLevel": "warning"
/**
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
* then the message will be written inside that file; otherwise, the message is instead logged according to
* the "logLevel" option.
*
* DEFAULT VALUE: false
*/
// "addToApiReportFile": false
}
// "TS2551": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
},
/**
* Configures handling of messages reported by API Extractor during its analysis.
*
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
*
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
*/
"extractorMessageReporting": {
// "default": {
// "logLevel": "warning"
// // "addToApiReportFile": false
// },
"ae-forgotten-export": {
"logLevel": "warning",
"addToApiReportFile": true
}
// "ae-extra-release-tag": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
},
/**
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
*
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
*
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
*/
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
// "addToApiReportFile": false
}
// "tsdoc-link-tag-unescaped-text": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
}
}
}
+46
View File
@@ -0,0 +1,46 @@
# Univer CLI Tool
## Usage
Install dependency
```sh
npm i -g @univer/cli
```
### Create Plugin
Create a Univer plugin named `my-plugin` from a template
```sh
univer-cli create my-plugin
```
start develop plugin
```sh
cd my-plugin
npm i
npm run dev
```
### Custom Build
```sh
univer-cli build --include sort comment
```
## API
### Create
Command
```sh
univer-cli create <plugin> [inner]
```
- plugin: Required parameters, specify a plugin name, Please use `param-case` format, such as `data-validation`
- inner: Optional parameter, use this tag inside the Univer repository. With this flag set, we will put the plugin in the packages directory, otherwise we will create a new plugin in the current directory where the script is executed by default
### Build
### Init
+44
View File
@@ -0,0 +1,44 @@
{
"name": "@univer/cli",
"version": "1.0.26",
"description": "Quickly build univer plugins, custom package univer cores and plugins",
"main": "./src/index.ts",
"module": "./src/index.ts",
"types": "./src/type.d.ts",
"publishConfig": {
"main": "./lib/index.js",
"module": "./lib/index.js",
"types": "./lib/index.d.ts"
},
"scripts": {
"dev": "tsc --project tsconfig.cli.json -w",
"cli": "ts-node --project tsconfig.cli.json src/cli.ts",
"build": "shx rm -rf lib/* && npx tsc --project tsconfig.cli.json && shx cp -r src/templates lib/templates"
},
"author": "DreamNum <msoffice2@126.com>",
"license": "MIT",
"files": [
"package.json",
"lib"
],
"dependencies": {
"chalk": "4.1.2",
"commander": "^9.0.0",
"ejs": "^3.0.2",
"execa": "^4.1.0",
"inquirer": "^7.1.0",
"shelljs": "^0.8.4",
"shx": "^0.3.4"
},
"devDependencies": {
"@types/ejs": "^3.0.2",
"@types/inquirer": "^6.5.0",
"@types/node": "^13.11.1",
"@types/shelljs": "^0.8.7",
"ts-node": "^10.2.1",
"typescript": "^4.4.1-rc"
},
"bin": {
"univer-cli": "./lib/cli.js"
}
}
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env node
import * as fs from 'fs';
import * as path from 'path';
import shell from 'shelljs';
import { FgGreen, FgRed, Reset } from './utils/color';
const execa = require('execa');
import { renderContent } from './utils/template';
import { generateRandomId } from './utils/tool';
import { IEventBus } from './utils/event-bus';
import { exec } from 'child_process';
// TODO 提供clear目录方法,用于下载完后删除项目
// dynamic 模式
interface IOption {
all?: boolean;
include?: string[];
exclude?: string[];
}
export interface IBuildJson {
[key: string]: string;
}
export interface IObj {
msg: string;
}
type PublishType = [IObj, number];
const defaultBuildList: IBuildJson = {
filter: '//',
sort: '//',
alternatingColors: '//',
comment: '//',
};
const allBuildList: IBuildJson = {
filter: ' ',
sort: ' ',
alternatingColors: ' ',
comment: ' ',
};
const CURR_DIR = process.cwd();
interface ILibOptions {
eventBus: IEventBus;
buildId: string;
}
export function build(options: IOption, libOptions?: ILibOptions) {
return new Promise((resolve, reject) => {
const buildList: IBuildJson = filterBuild(options);
const { eventBus, buildId } = libOptions || {};
// When we provide nodejs interface, support library mode, create a new project directory with a random name to prevent directory conflicts from running by multiple users at the same time.
const id = buildId ? buildId : '';
const randomFolderName = 'univer-custom-build-' + id;
eventBus?.publish<PublishType>('progress', { msg: 'Pull template files' }, 0.1);
copyTemplate(randomFolderName);
eventBus?.publish<PublishType>('progress', { msg: 'Update custom configuration' }, 0.2);
updateEntry(buildList, randomFolderName);
eventBus?.publish<PublishType>('progress', { msg: 'Install dependencies' }, 0.3);
buildScript(resolve, reject, randomFolderName, eventBus);
});
}
function filterBuild(options: IOption): IBuildJson {
const { all, include, exclude } = options;
let filterBuildList: IBuildJson = defaultBuildList;
if (all) {
filterBuildList = allBuildList;
}
if (include) {
filterBuildList = defaultBuildList;
include.forEach((plugin) => {
if (plugin in defaultBuildList) {
filterBuildList[plugin] = ' ';
}
});
}
if (exclude) {
filterBuildList = allBuildList;
exclude.forEach((plugin) => {
if (plugin in allBuildList) {
filterBuildList[plugin] = '//';
}
});
}
return filterBuildList;
}
function copyTemplate(folderName: string) {
const templatePath = path.join(__dirname, 'templates/univer-custom-build');
copyFolderRecursiveSync(templatePath, CURR_DIR);
fs.rename(path.join(CURR_DIR, 'univer-custom-build'), path.join(CURR_DIR, folderName), () => {});
}
function updateEntry(json: IBuildJson, folderName: string) {
const entryFolder = path.join(CURR_DIR, folderName);
const entryFile = path.join(entryFolder, 'index-template.ts');
// read file content and transform it using template engine
let contents = fs.readFileSync(entryFile, 'utf8');
contents = renderContent(contents, json);
// write file to destination folder
let writePath = path.join(entryFolder, 'src/index.custom.ts');
fs.writeFileSync(writePath, contents, 'utf8');
}
async function buildScript(resolve: Function, reject: Function, folderName: string, eventBus?: IEventBus) {
shell.cd(folderName);
// execa('cd', ['univer-custom-build'], { stdio: 'inherit' })
// The host environment may be production, which will cause development dependencies to fail to be installed, reference https://docs.npmjs.com/cli/v8/commands/npm-install
await execa('npm', ['i', '--production=false', '--prefer-offline'], { stdio: 'inherit' });
eventBus?.publish<PublishType>('progress', { msg: 'Build source code' }, 0.8);
try {
await execa('npm', ['run', 'build'], { stdio: 'inherit' });
console.log(`\n${FgGreen}Custom build success!${Reset} \n`);
eventBus?.publish<PublishType>('progress', { msg: 'Build complete' }, 1.0);
resolve('success');
} catch (error) {
console.error(`\n${FgRed}Custom build fail!${Reset} \n`);
eventBus?.publish<PublishType>('progress', { msg: 'Build interrupt' }, 0);
reject('fail');
}
}
function copyFileSync(source: string, target: string) {
var targetFile = target;
// If target is a directory, a new file with the same name will be created
if (fs.existsSync(target)) {
if (fs.lstatSync(target).isDirectory()) {
targetFile = path.join(target, path.basename(source));
}
}
fs.writeFileSync(targetFile, fs.readFileSync(source));
}
/**
* Reference https://stackoverflow.com/a/26038979
* @param source
* @param target
*/
function copyFolderRecursiveSync(source: string, target: string) {
var files = [];
// Check if folder needs to be created or integrated
var targetFolder = path.join(target, path.basename(source));
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder);
}
// Copy
if (fs.lstatSync(source).isDirectory()) {
files = fs.readdirSync(source);
files.forEach(function (file) {
var curSource = path.join(source, file);
if (fs.lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
copyFileSync(curSource, targetFolder);
}
});
}
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env node
import { Command } from 'commander';
import { build } from './build';
import { create } from './create';
const program = new Command();
program
.command('build')
.option('-a, --all', 'Package the core and all plugins together')
.option('-i, --include <plugins...>', 'List plugins that need to be packaged')
.option('-e, --exclude <plugins...>', 'List plugins that do not need to be packaged')
.description('Package the core and plugins together,core and base plugins are packaged by default')
.action((options) => {
build(options);
});
// TODO:check npm package name conflict
program
.command('create <plugin> [inner]')
.description('Create a Univer plugin from a template')
.action((plugin) => {
create(plugin);
});
program.parse(process.argv);
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env node
/**
* Quickly generate plug-in directory
*
* Reference from https://github.com/DuwainevanDriel/project-template-generator
*/
import chalk from 'chalk';
import * as fs from 'fs';
import * as path from 'path';
import * as template from './utils/template';
interface CliOptions {
projectName: string;
templateName: string;
templatePath: string;
tartgetPath: string;
}
// interface IOptions {
// inner?: boolean;
// }
export function create(plugin: string) {
console.log('plugin create', plugin);
const cliOptions: CliOptions = {
//@ts-ignore
projectName: '',
//@ts-ignore
templateName: '',
templatePath: '',
tartgetPath: '',
};
const SKIP_FILES = ['node_modules', '.template.json'];
const CURR_DIR = process.cwd();
const inner = process.argv[4];
let DESTINATION_DIR = '';
if (inner === 'inner') {
// core repository create plugin
DESTINATION_DIR = path.join(CURR_DIR, '../packages');
} else {
// cli
DESTINATION_DIR = path.join(CURR_DIR, '');
}
const TEMP_PLUGIN_NAME = 'Sort';
const projectChoice = 'plugin-temp';
const projectValue = covertToCamelCase(plugin);
const projectUpperValue = covertToPascalCase(projectValue);
const projectName = covertToParamCase(projectValue);
//@ts-ignore
const templatePath = path.join(__dirname, 'templates', projectChoice);
// sheets-plugin-data-validation => sheets-plugin-data-validation
//@ts-ignore
const tartgetPath = path.join(DESTINATION_DIR, projectName);
cliOptions.projectName = projectName;
cliOptions.templateName = projectChoice;
cliOptions.templatePath = templatePath;
cliOptions.tartgetPath = tartgetPath;
if (!createProject(tartgetPath)) {
return;
}
//@ts-ignore
createDirectoryContents(templatePath, projectName, cliOptions);
// postProcess(cliOptions);
devTips(projectName);
function createDirectoryContents(templatePath: string, projectName: string) {
// read all files/folders (1 level) from template folder
const filesToCreate = fs.readdirSync(templatePath);
// loop each file/folder
filesToCreate.forEach((file) => {
const origFilePath = path.join(templatePath, file);
// get stats about the current file
const stats = fs.statSync(origFilePath);
// skip files that should not be copied
if (SKIP_FILES.indexOf(file) > -1) return;
if (stats.isFile()) {
// read file content and transform it using template engine
let contents = fs.readFileSync(origFilePath, 'utf8');
contents = template.render(contents, { projectValue, projectUpperValue, projectName: cliOptions.projectName });
// write file to destination folder
let writePath = path.join(DESTINATION_DIR, projectName, file);
// change file name
if (writePath.indexOf(TEMP_PLUGIN_NAME) > -1) {
writePath = writePath.replace(TEMP_PLUGIN_NAME, projectUpperValue);
}
fs.writeFileSync(writePath, contents, 'utf8');
} else if (stats.isDirectory()) {
// create folder in destination folder
let writeFolderPath = path.join(DESTINATION_DIR, projectName, file);
// change folder name (Not recommended. Because the directory of each plug-in should be the same)
// if (writeFolderPath.indexOf(TEMP_PLUGIN_NAME) > -1) {
// writeFolderPath = writeFolderPath.replace(TEMP_PLUGIN_NAME, projectUpperValue);
// }
fs.mkdirSync(writeFolderPath);
// const destination = path.join(CURR_DIR, 'packages');
// fs.mkdirSync(path.join(destination, projectName, file));
// copy files/folder inside current folder recursively
createDirectoryContents(path.join(templatePath, file), path.join(projectName, file));
}
});
}
}
function createProject(projectPath: string) {
if (fs.existsSync(projectPath)) {
console.log(chalk.red(`Folder ${projectPath} exists. Delete or use another name.`));
return false;
}
fs.mkdirSync(projectPath);
return true;
}
function devTips(projectName: string) {
// pnpm run --filter @univer/sheets-plugin-<%= projectValue %> dev
console.log(chalk.green(`\nSuccess!\n`));
console.log(chalk.white(`Install Dependencies: \n`, chalk.green(`pnpm i --filter @univer/${projectName}\n`)));
console.log(chalk.white(`Develop: \n`, chalk.green(`pnpm run --filter @univer/${projectName} dev\n`)));
}
function covertToPascalCase(str: string) {
return str
.replace(/\b\w/g, (match) => {
return match.toUpperCase();
})
.replace(/-/g, '');
}
function covertToParamCase(str: string, prefix = 'plugin') {
return prefix + str.replace(/(-\w|[A-Z]|\b\w)/g, (match) => (match.indexOf('-') > -1 ? match.toLowerCase() : `-${match.toLowerCase()}`));
}
/**
*
* Reference: https://stackoverflow.com/a/2970667
* @param str
* @returns
*/
function covertToCamelCase(str: string) {
return str
.replace(/(?:\b\w)/g, function (match, index) {
return index === 0 ? match.toLowerCase() : match.toUpperCase();
})
.replace(/-/g, '');
}
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env node
import { build, IObj } from './build';
import { create } from './create';
import { EventBus, IEventBus } from './utils/event-bus';
export { build, create, EventBus};
export type {IObj,IEventBus}
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env node
/**
* Quickly generate plug-in directory
*
* Reference from https://github.com/DuwainevanDriel/project-template-generator
*/
import chalk from 'chalk';
import * as fs from 'fs';
import * as inquirer from 'inquirer';
import * as path from 'path';
import * as template from './utils/template';
interface CliOptions {
projectName: string;
templateName: string;
templatePath: string;
tartgetPath: string;
}
interface IOptions {
inner?: boolean;
}
export function init(options: IOptions) {
console.log('template init', options);
const cliOptions: CliOptions = {
//@ts-ignore
projectName: '',
//@ts-ignore
templateName: '',
templatePath: '',
tartgetPath: '',
};
const SKIP_FILES = ['node_modules', '.template.json'];
const CURR_DIR = process.cwd();
let DESTINATION_DIR = '';
if (options.inner == true) {
// core repository create plugin
DESTINATION_DIR = path.join(CURR_DIR, '../packages');
} else {
// cli
DESTINATION_DIR = path.join(CURR_DIR, '');
}
const TEMP_PLUGIN_NAME = 'Sort';
let projectValue: string = '';
let projectUpperValue: string = '';
const CHOICES = fs.readdirSync(path.join(__dirname, 'templates'));
const QUESTIONS = [
{
name: 'template',
type: 'list',
message: 'What template would you like to use?',
choices: CHOICES,
},
{
name: 'name',
type: 'input',
message: 'Please input a new plugin name:',
},
];
inquirer.prompt(QUESTIONS).then((answers) => {
const projectChoice = answers['template'] as string;
projectValue = answers['name'] as string;
projectUpperValue = capitalizeFirstLetter(projectValue);
const projectName = ('plugin-' + projectValue).replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
//@ts-ignore
const templatePath = path.join(__dirname, 'templates', projectChoice);
// sheets-plugin-data-validation => sheets-plugin-data-validation
//@ts-ignore
const tartgetPath = path.join(DESTINATION_DIR, projectName);
cliOptions.projectName = projectName;
cliOptions.templateName = projectChoice;
cliOptions.templatePath = templatePath;
cliOptions.tartgetPath = tartgetPath;
if (!createProject(tartgetPath)) {
return;
}
//@ts-ignore
createDirectoryContents(templatePath, projectName, cliOptions);
// postProcess(cliOptions);
devTips(projectName);
});
function createDirectoryContents(templatePath: string, projectName: string) {
// read all files/folders (1 level) from template folder
const filesToCreate = fs.readdirSync(templatePath);
// loop each file/folder
filesToCreate.forEach((file) => {
const origFilePath = path.join(templatePath, file);
// get stats about the current file
const stats = fs.statSync(origFilePath);
// skip files that should not be copied
if (SKIP_FILES.indexOf(file) > -1) return;
if (stats.isFile()) {
// read file content and transform it using template engine
let contents = fs.readFileSync(origFilePath, 'utf8');
contents = template.render(contents, { projectValue, projectUpperValue, projectName: cliOptions.projectName });
// write file to destination folder
let writePath = path.join(DESTINATION_DIR, projectName, file);
// change file name
if (writePath.indexOf(TEMP_PLUGIN_NAME) > -1) {
writePath = writePath.replace(TEMP_PLUGIN_NAME, projectUpperValue);
}
fs.writeFileSync(writePath, contents, 'utf8');
} else if (stats.isDirectory()) {
// create folder in destination folder
let writeFolderPath = path.join(DESTINATION_DIR, projectName, file);
// change folder name (Not recommended. Because the directory of each plug-in should be the same)
// if (writeFolderPath.indexOf(TEMP_PLUGIN_NAME) > -1) {
// writeFolderPath = writeFolderPath.replace(TEMP_PLUGIN_NAME, projectUpperValue);
// }
fs.mkdirSync(writeFolderPath);
// const destination = path.join(CURR_DIR, 'packages');
// fs.mkdirSync(path.join(destination, projectName, file));
// copy files/folder inside current folder recursively
createDirectoryContents(path.join(templatePath, file), path.join(projectName, file));
}
});
}
}
function createProject(projectPath: string) {
if (fs.existsSync(projectPath)) {
console.log(chalk.red(`Folder ${projectPath} exists. Delete or use another name.`));
return false;
}
fs.mkdirSync(projectPath);
return true;
}
function capitalizeFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function devTips(projectName: string) {
// pnpm run --filter @univer/sheets-plugin-<%= projectValue %> dev
console.log(chalk.green(` SUCCESS!\n\n`), chalk.white(`Check directory packages/${projectName}, run the following command to develop: \n\n`), chalk.green(`pnpm run --filter @univer/${projectName} dev`));
}
@@ -0,0 +1,51 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
parser: '@typescript-eslint/parser',
extends: [
'airbnb-typescript',
'prettier',
'eslint:recommended',
'plugin:import/recommended',
'plugin:import/typescript',
],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
project: './tsconfig.json',
},
plugins: ['@typescript-eslint', 'prettier', 'import'],
rules: {
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'import/prefer-default-export': 'off',
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
// turn on errors for missing imports
'import/no-unresolved': 'error',
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
alwaysTryTypes: true, // always try to resolve config under `<root>@config` directory even it doesn't contain any source code, like `@config/unist`
// Choose from one of the "project" configs below or omit to use <root>/tsconfig.json by default
// use <root>/path/to/folder/tsconfig.json
project: './tsconfig.json',
},
},
},
};
+101
View File
@@ -0,0 +1,101 @@
#ide
*.DS_Store
.idea
.vscode
#npm
package-lock.json
dist
lib
docs/.vuepress/dist
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Runtime config
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL History
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
#lerna-changelog
.changelog
#editor workspace file
.idea/
.DS_Store
.history
# examples
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.env.local
.env.development.local
.env.test.local
.env.production.local
@@ -0,0 +1,7 @@
module.exports = {
singleQuote: true,
trailingComma: 'es5',
tabWidth: 4,
semi: true,
printWidth: 180,
};
@@ -0,0 +1,55 @@
# style-universheet
简体中文 | [English](./README.md)
## 介绍
UniverSheet <%= projectUpperValue %>插件
### 安装
```bash
npm i @univer/<%= projectName %>
```
### 使用
```js
impport {<%= projectUpperValue %>} from '@univer/<%= projectName %>'
const univerSheet = new UniverSheet();
univerSheet.installPlugin(new <%= projectUpperValue %>());
```
## 本地开发
### 环境
- [Node.js](https://nodejs.org/en/) Version >= 10
- [npm](https://www.npmjs.com/) Version >= 6
### 安装
```
pnpm install
```
### 开发
```
# 当前目录 ./packages/<%= projectName %>/
npm run dev
# 或者项目根目录 ./
pnpm run --filter @univer/<%= projectName %> dev
```
### 打包
```
# 当前目录 ./packages/<%= projectName %>/
npm run build
# 或者根目录 ./
pnpm run --filter @univer/<%= projectName %> build
```
+55
View File
@@ -0,0 +1,55 @@
# style-universheet
English| [简体中文](./README-zh.md)
## Introduction
UniverSheet Plugin <%= projectUpperValue %>
### Installation
```bash
npm i @univer/<%= projectName %>
```
### Usage
```js
impport {<%= projectUpperValue %>} from '@univer/<%= projectName %>'
const univerSheet = new UniverSheet();
univerSheet.installPlugin(new <%= projectUpperValue %>());
```
## Local development
### Requirements
- [Node.js](https://nodejs.org/en/) Version >= 10
- [npm](https://www.npmjs.com/) Version >= 6
### Installation
```
pnpm install
```
### Development
```
# Current directory ./packages/<%= projectName %>/
npm run dev
# Or project root directory ./
pnpm run --filter @univer/<%= projectName %> dev
```
### Package
```
# Current directory ./packages/<%= projectName %>/
npm run build
# Or root directory ./
pnpm run --filter @univer/<%= projectName %> build
```
+24
View File
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>UniverSheet</title>
<style>
html,
body {
height: 100%;
margin: 0;
}
</style>
</head>
<body>
<div id="app"></div>
<div id="universheet-demo" style="height: 100%;"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
@@ -0,0 +1,18 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
moduleFileExtensions: ['js', 'ts', 'tsx'],
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
'^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207
},
globals: {
'ts-jest': {
tsConfig: 'tsconfig.test.json',
},
},
};
@@ -0,0 +1,59 @@
{
"name": "@univer/<%= projectName %>",
"version": "0.0.1",
"description": "UniverSheet normal <%= projectName %>",
"keywords": [],
"author": "DreamNum <msoffice2@126.com>",
"license": "MIT",
"main": "./src/index.ts",
"module": "./src/index.ts",
"types": "./src/Types/index.d.ts",
"exports": {
".": "./src/index.ts",
"./*": "./src/*"
},
"publishConfig": {
"main": "./lib/index.umd.js",
"module": "./lib/index.es.js",
"types": "./lib/index.d.ts"
},
"directories": {
"lib": "lib"
},
"scripts": {
"test": "jest",
"dev": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
"devDependencies": {
"@preact/preset-vite": "2.0.0",
"@testing-library/preact": "^2.0.1",
"@types/jest": "^27.0.1",
"@types/node": "^16.4.6",
"@vitejs/plugin-legacy": "^1.5.1",
"eslint": "^8.22.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"less": "^4.1.1",
"prettier": "^2.3.2",
"ts-jest": "29.0.1",
"ts-node": "^10.2.1",
"typescript": "^4.4.1-rc",
"vite": "^2.4.4"
},
"dependencies": {
"@univer/base-component": "workspace:^0.0.1",
"@univer/base-render": "workspace:^0.0.1",
"@univer/core": "workspace:^0.0.1",
"@univer/base-sheets": "workspace:^0.0.1",
"@univer/sheets-plugin-filter": "workspace:^0.0.1",
"@univer/style-universheet": "workspace:^0.0.1",
"preact": "^10.5.14"
}
}
@@ -0,0 +1,4 @@
export * from './Const';
export * from './Enum';
export * from '../Locale';
export * from './Shared';
@@ -0,0 +1,9 @@
export default {
<%= projectValue %>Label: '<%= projectUpperValue %>',
<%= projectValue %>RightLabel: 'more <%= projectUpperValue %>',
<%= projectValue %>: {
one:'one',
two:'two',
three:'three',
},
};
@@ -0,0 +1,4 @@
import zh from './zh';
import en from './en';
export { zh, en };
@@ -0,0 +1,9 @@
export default {
<%= projectValue %>Label: '插件',
<%= projectValue %>RightLabel: '更多 <%= projectUpperValue %>',
<%= projectValue %>: {
one:'一',
two:'二',
three:'三',
},
};
@@ -0,0 +1,47 @@
import { Context, Plugin, PLUGIN_NAMES } from '@univer/core';
import { <%= projectUpperValue %>Button } from './View/UI/<%= projectUpperValue %>Button';
import { zh, en } from './Locale';
import { IToolBarItemProps, ISlotElement } from '@univer/base-component';
import { IOCContainer } from '@univer/core';
interface IPluginConfig {}
interface IConfig {}
export class <%= projectUpperValue %>Plugin extends Plugin {
constructor(config ?: IPluginConfig) {
super('plugin<%= projectUpperValue %>');
}
initialize(): void {
const context = this.getContext();
/**
* load more Locale object
*/
context.getLocale().load({
en: en,
zh: zh,
});
const config: IConfig = {};
const item: IToolBarItemProps = {
locale: '<%= projectValue %>',
type: ISlotElement.JSX,
show: true,
label: <<%= projectUpperValue %>Button config={ config } />
}
context.getPluginManager().getPluginByName<SpreadsheetPlugin>(PLUGIN_NAMES.SPREADSHEET)?.addButton(item)
}
onMapping(IOC: IOCContainer): void {}
onMounted(ctx: Context): void {
this.initialize()
}
onDestroy(): void {}
}
+12
View File
@@ -0,0 +1,12 @@
/// <reference types="vite/client" />
export * from '../index';
declare module '@univer/<%= projectName %>' {}
import JSX = preact.JSX;
// use css module
declare module '*.less' {
const resource: { [key: string]: string };
export = resource;
}
@@ -0,0 +1,104 @@
import { Nullable, Observer, WorkBook } from '@univer/core';
import { ISelectButton, IToolBarItemProps, Component } from '@univer/base-component';
interface IProps {}
// Types for state
interface IState {
<%= projectValue %>: IToolBarItemProps;
}
export class <%= projectUpperValue %>Button extends Component<IProps, IState> {
private _localeObserver: Nullable<Observer<WorkBook>>;
initialize(props: IProps) {
const OrderASCIcon = this.getComponentRender().renderFunction('OrderASCIcon');
const OrderDESCIcon = this.getComponentRender().renderFunction('OrderDESCIcon');
const OrderIcon = this.getComponentRender().renderFunction('OrderIcon');
const NextIcon = this.getComponentRender().renderFunction('NextIcon');
const <%= projectValue %> = {
locale: '<%= projectValue %>',
type: 'select',
label: <OrderASCIcon />,
icon: <NextIcon />,
show: true,
border: false,
selectType: ISelectButton.DOUBLE,
needChange: false,
children: [
{
locale: '<%= projectValue %>.one',
icon: <OrderASCIcon />,
},
{
locale: '<%= projectValue %>.two',
icon: <OrderDESCIcon />,
},
{
locale: '<%= projectValue %>.three',
icon: <OrderIcon />,
},
],
};
this.state = {
<%= projectValue %>: <%= projectValue %>,
};
}
/**
* init
*/
componentWillMount() {
this.setLocale();
// subscribe Locale change event
this._localeObserver = this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable','core')?.add(() => {
this.setLocale();
});
}
/**
* destory
*/
componentWillUnmount() {
this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable','core')?.remove(this._localeObserver);
}
/**
* set text by config setting and Locale message
*/
setLocale() {
const locale = this._context.getLocale();
this.setState((prevState: IState) => {
let item = prevState.<%= projectValue %>;
// set current Locale string for tooltip
item.tooltip = locale.get(`${item.locale}Label`);
item.tooltipRight = locale.get(`${item.locale}RightLabel`);
// set current Locale string for select
item.children?.forEach((ele: IToolBarItemProps) => {
if (ele.locale) {
ele.label = locale.get(`${ele.locale}`);
}
});
item.label = typeof item.label == 'object' ? item.label : item.children![0].label;
return {
<%= projectValue %>: item,
};
});
}
/**
* Render the component's HTML
*
* @returns {void}
*/
render(props: IProps, state: IState) {
const { <%= projectValue %> } = state;
const Select = this.getComponentRender().renderFunction('Select');
// Set Provider for entire Container
return <Select tooltip={<%= projectValue %>.tooltip} tooltipRight={<%= projectValue %>.tooltipRight} border={<%= projectValue %>.border} isDouble={<%= projectValue %>.isDouble} needChange={<%= projectValue %>.needChange} key={<%= projectValue %>.locale} children={<%= projectValue %>.children as IToolBarItemProps[]} label={<%= projectValue %>.label} icon={<%= projectValue %>.icon} />;
}
}
@@ -0,0 +1,3 @@
import { <%= projectUpperValue %>Plugin } from './<%= projectUpperValue %>Plugin';
export { <%= projectUpperValue %>Plugin };
@@ -0,0 +1,22 @@
import { defaultWorkbookData, UniverSheet } from '@univer/core';
import { RenderEngine } from '@univer/base-render';
import { SpreadsheetPlugin } from '@univer/base-sheets';
import { UniverComponentSheet } from '@univer/style-universheet';
import { FilterPlugin } from '@univer/sheets-plugin-filter';
import { <%= projectUpperValue %>Plugin } from './<%= projectUpperValue %>Plugin';
const uiDefaultConfigUp = {
containerId: 'universheet-demo',
layout: 'auto',
};
const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData);
univerSheetUp.installPlugin(new RenderEngine());
univerSheetUp.installPlugin(new UniverComponentSheet());
univerSheetUp.installPlugin(new SpreadsheetPlugin(uiDefaultConfigUp));
univerSheetUp.installPlugin(new FilterPlugin());
univerSheetUp.installPlugin(new <%= projectUpperValue %>Plugin());
+1
View File
@@ -0,0 +1 @@
import JSX = preact.JSX;
@@ -0,0 +1,31 @@
{
"compilerOptions": {
"rootDir": "./",
"outDir": "lib",
"target": "ESNext",
"lib": ["ESNext", "DOM", "WebWorker"],
"module": "ESNext",
"moduleResolution": "Node", //This value is needed for Node.js.
"declaration": true,
"declarationDir": "./lib",
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true,
"strict": true,
"allowJs": true,
"baseUrl": "./",
"skipLibCheck": true,
"esModuleInterop": true, // auto allowSyntheticDefaultImports tru
"suppressImplicitAnyIndexErrors": true,
"experimentalDecorators": true,
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
"strictPropertyInitialization": false,
"types": ["jest", "node", "@types/jest"]
},
"include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"]
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"composite": true
}
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
@@ -0,0 +1,44 @@
import { defineConfig } from 'vite';
import path from 'path';
import preact from '@preact/preset-vite';
import legacy from '@vitejs/plugin-legacy';
import { name, version } from './package.json';
const resolve = (url: string) => path.resolve(__dirname, url);
export default defineConfig({
build: {
// lib: {
// entry: resolve('src/index.ts'),
// name: 'BaseStyleUniver',
// formats: ['es', 'umd'],
// fileName: 'index',
// },
// outDir: './lib',
},
define: {
pkgJson: { name, version },
},
css: {
modules: {
localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor
generateScopedName: 'univer-[local]', // custom prefix class name
},
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
},
server: {
port: 3103,
open: true, // Automatically open the app in the browser on server start.
},
plugins: [
preact(),
legacy({
targets: ['ie >= 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
}),
],
});
+2
View File
@@ -0,0 +1,2 @@
export * from './index';
declare module '@univer/cli' {}
+27
View File
@@ -0,0 +1,27 @@
// colors reference of text to command when running node.js application
// https://stackoverflow.com/a/41407246
export const Reset = '\x1b[0m';
export const Bright = '\x1b[1m';
export const Dim = '\x1b[2m';
export const Underscore = '\x1b[4m';
export const Blink = '\x1b[5m';
export const Reverse = '\x1b[7m';
export const Hidden = '\x1b[8m';
export const FgBlack = '\x1b[30m';
export const FgRed = '\x1b[31m';
export const FgGreen = '\x1b[32m';
export const FgYellow = '\x1b[33m';
export const FgBlue = '\x1b[34m';
export const FgMagenta = '\x1b[35m';
export const FgCyan = '\x1b[36m';
export const FgWhite = '\x1b[37m';
export const BgBlack = '\x1b[40m';
export const BgRed = '\x1b[41m';
export const BgGreen = '\x1b[42m';
export const BgYellow = '\x1b[43m';
export const BgBlue = '\x1b[44m';
export const BgMagenta = '\x1b[45m';
export const BgCyan = '\x1b[46m';
export const BgWhite = '\x1b[47m';
+121
View File
@@ -0,0 +1,121 @@
/**
* Event Bus
*
* Reference https://www.thisdot.co/blog/how-to-implement-an-event-bus-in-typescript
*/
interface ICallbackList {
[id: string]: Function;
}
interface IEventObject {
[eventName: string]: ICallbackList;
}
interface ISubscribe {
unSubscribe: () => void;
}
export interface IEventBus {
publish<T extends any[]>(eventName: string, ...args: T): void;
subscribe(eventName: string, callback: Function): ISubscribe;
subscribeOnce(eventName: string, callback: Function): ISubscribe;
clear(eventName: string): void;
}
export class EventBus implements IEventBus {
private _eventObject: IEventObject;
private _callbackId: number;
constructor() {
// initialize event list
this._eventObject = {};
// id of the callback function list
this._callbackId = 0;
}
// publish event
publish<T extends any[]>(eventName: string, ...args: T): void {
// Get all the callback functions of the current event
const callbackObject = this._eventObject[eventName];
if (!callbackObject) return console.warn(eventName + ' not found!');
// execute each callback function
for (let id in callbackObject) {
// pass parameters when executing
callbackObject[id](...args);
// The callback function that is only subscribed once needs to be deleted
if (id[0] === 'd') {
delete callbackObject[id];
}
}
}
// Subscribe to events
subscribe(eventName: string, callback: Function): ISubscribe {
// initialize this event
if (!this._eventObject[eventName]) {
// Use object storage to improve the efficiency of deletion when logging out the callback function
this._eventObject[eventName] = {};
}
const id = this._callbackId++;
// store the callback function of the subscriber
// callbackId needs to be incremented after use for the next callback function
this._eventObject[eventName][id] = callback;
// Every time you subscribe to an event, a unique unsubscribe function is generated
const unSubscribe = () => {
// clear the callback function of this subscriber
delete this._eventObject[eventName][id];
// If this event has no subscribers, also clear the entire event object
if (Object.keys(this._eventObject[eventName]).length === 0) {
delete this._eventObject[eventName];
}
};
return { unSubscribe };
}
// only subscribe once
subscribeOnce(eventName: string, callback: Function): ISubscribe {
// initialize this event
if (!this._eventObject[eventName]) {
// Use object storage to improve the efficiency of deletion when logging out the callback function
this._eventObject[eventName] = {};
}
// Callback function marked as subscribe only once
const id = 'd' + this._callbackId++;
// store the callback function of the subscriber
// callbackId needs to be incremented after use for the next callback function
this._eventObject[eventName][id] = callback;
// Every time you subscribe to an event, a unique unsubscribe function is generated
const unSubscribe = () => {
// clear the callback function of this subscriber
delete this._eventObject[eventName][id];
// If this event has no subscribers, also clear the entire event object
if (Object.keys(this._eventObject[eventName]).length === 0) {
delete this._eventObject[eventName];
}
};
return { unSubscribe };
}
// clear event
clear(eventName: string): void {
// If no event name is provided, all events are cleared by default
if (!eventName) {
this._eventObject = {};
return;
}
// clear the specified event
delete this._eventObject[eventName];
}
}
+30
View File
@@ -0,0 +1,30 @@
import * as ejs from 'ejs';
import { IBuildJson } from '../build';
/**
* Place where you need to replace the variable
*
* <%= projectUpperValue %>
<%= projectValue %>
*/
export interface TemplateData {
projectValue: string;
projectUpperValue: string;
projectName: string;
}
/**
*
* repalce value in content
*
* @param content
* @param data
* @returns
*/
export function render(content: string, data: TemplateData) {
return ejs.render(content, data);
}
export function renderContent(content: string, data: IBuildJson) {
return ejs.render(content, data);
}
+6
View File
@@ -0,0 +1,6 @@
export function generateRandomId(): string {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return new Date().getTime() + S4() + S4();
}
+68
View File
@@ -0,0 +1,68 @@
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": false /* Generates corresponding '.map' file. */,
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "lib" /* Redirect output structure to the directory. */,
"rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
"types": ["@types/ejs", "@types/inquirer", "@types/node", "@types/shelljs"] /* Type declaration files to be included in compilation. */,
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
//"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["src/**/*"],
"exclude": ["src/templates/**/*"]
}
+20
View File
@@ -0,0 +1,20 @@
# 关于
## 贡献者
## 赞助商
## 社区案例
## 开源软件申明
我们站在巨人的肩膀上构建新的应用,非常感谢这些开源贡献者们的无私奉献。
- [preact](https://preactjs.com)
- [dayjs](https://github.com/iamkun/dayjs)
- [numeral](https://github.com/adamwdraper/Numeral-js)
- [reflect-metadata](https://github.com/rbuckton/reflect-metadata)
- [es6-proxy-polyfill](https://github.com/ambit-tsai/es6-proxy-polyfill)
- [react-colorful](https://github.com/omgovich/react-colorful)
- [css-vars-ponyfill](https://github.com/jhildenbiddle/css-vars-ponyfill)
- [csstype](https://github.com/frenic/csstype)
+33
View File
@@ -0,0 +1,33 @@
# 核心设计
## Observer
每一个 UniverSheet 实例都会有一个 ObserverManager 来这个实例的 ObserverObserverManager 挂载在 context 上。
在核心中,Observer 是初始化内置的,直接从 ObserverManager 实例 调用即可,比如更新国际化信息的 Observer `onAfterChangeUILocaleObservable`
在 Plugin 基类 中,也抽象了便捷的方法来创建和销毁的 Observer
Plugin Observer 方法
- addObserver: 增加 Observer,使用 addObserver 需要在插件初始化的时候手动调用,指定 Observer 名称,Plugin 基类会帮您指定 namespace 为当前插件
- removeObserver: 销毁 Observer,此 API 会在插件销毁的时候自动在 onDestroy 中调用,插件开发者无需手动调用,如果插件开发者要手动更新 onDestroy 函数,需要使用 `super.onDestroy()` 来调用 Plugin 基类的销毁方法
- getObserver:获取 Observer,支持在 插件中快捷获取你想要的 Observer,如果插件中的其他模块也需要获取 Observer,可以将插件实例或者核心 context 注入到其他模块中来调用 Observer
```js
// 使用插件的 getObserver
pluginInstance.getObserver('observeName');
// 或者
// 使用 context 的 ObserverManager
this.context
.getObserverManager()
.getObserver('onAfterChangeFontFamilyObservable', 'styleUniverSheet')
?.add(() => {});
```
API
案例;设置单元格值,设置样式
base-sheets 引入 UI 组件库使用时,推荐分离业务逻辑和 UI,使得能够针对业务逻辑做单元测试,这些业务逻辑包括一些纯输入输出的计算、转换方法等。
@@ -0,0 +1,250 @@
# 前端插件化架构在 UniverSheet 3.0 的实现
## 需求
在前端开发中,当你的系统功能逐渐增多,模块间的交互就会越来越复杂,如果没有做提前设计,代码很容易变得臃肿和难以阅读。
这时候就需要考虑加入设计模式了,通常来说,只需要加入几种常见的设计模式,比如观察者模式、命令模式,就可以让模块间解耦,让你的系统变得更加健壮、扩展性更强。
UniverSheet 2.0 很少用到设计模式,扩展性不好,模块拆分不够细致,所以我们 UniverSheet 3.0 重构的时候,重点考虑了扩展性,设计了一种插件化架构,或者叫微内核架构。有了插件,扩展自己的功能就方便了,而且不用改造核心库,可以和官方保持同步。下面就来介绍下我们团队在重构 UniverSheet 时候的思考。
## 设计
我们改造的目标是抽离核心模块,构造可扩展的插件系统,支持多实例,npm 引入,增强 SDK 等。重点就是设计插件系统。
实现一个插件系统,主要解决 3 块内容
1. 插件初始化,插件的逻辑
2. 插件管理,安装、卸载等
3. 插件和核心通信,插件和插件通信
下面简要介绍 UniverSheet 的插件设计
## 原理
### 插件初始化
首先我们在核心构造一个`Plugin`基类,将插件要用到基础属性和方法放在这里,比如插件名称,插件的生命周期。
初始化即安装时调用 `onMounted` 方法,卸载时调用 `onDestroy` 方法。
> 为便于理解,以下部分代码经过简化,我们主要讲解下思路
```ts
export abstract class Plugin {
protected _name: string;
protected constructor(name: string) {
this._name = name;
}
getName(): string {
return this._name;
}
abstract onMounted(): void;
abstract onDestroy(): void;
// 其它方法
}
```
有了基类,接着就是实现一个插件,任何的插件都需要继承这个`Plugin`类,比如排序插件,需要传入插件名称,内部自行实现好初始化的时候具体插件要做的事情
```ts
export class SortPlugin extends Plugin {
constructor() {
super('sort');
}
// 初始化
onMounted(): void {
// 插件具体初始化逻辑
}
// 卸载
onDestroy(): void {}
// 其它方法
}
```
### 插件管理
有了具体的插件之后,需要安装插件到核心,我们先提供了 `PluginManager` 的插件管理类,专门负责插件的存储、安装、卸载。
思路是每个插件都存储到数组中,安装的时候,执行插件内部的初始化方法即可,卸载的时候,再执行内部的卸载方法并把插件移除掉。管理类主要是起到存储插件和调用插件内部方法的作用。
```ts
export class PluginManager {
private _plugins: Plugin[];
constructor() {}
_destroy(plugins: Plugin[]) {
plugins.forEach((plugin: Plugin) => {
plugin.onDestroy();
});
}
_initialize(plugins: Plugin[]) {
plugins.forEach((plugin: Plugin) => {
plugin.onMounted();
});
}
// 安装
install(plugin: Plugin) {
const { _plugins } = this;
_plugins.push(plugin);
this._initialize([plugin]);
}
// 卸载
uninstall(name: string) {
const { _plugins } = this;
const index = _plugins.findIndex((item) => item.getName() === name);
if (index > -1) {
const remove = _plugins.splice(index, 1);
this._destroy(remove);
}
}
// 其它方法
}
```
我们还在`UniverSheet`实例上提供了方便安装插件的 API `installPlugin`
> 提示:这种包装的 API 随处可见,这让我们 API 有统一的出口,更方便使用
```ts
export class UniverSheet {
private _context: Context;
installPlugin(plugin: Plugin): void {
this._context.getPluginManager().install(plugin);
}
uninstallPlugin(name: string): void {
this._context.getPluginManager().uninstall(name);
}
// 其它方法
}
```
### 插件通信
我们有两种通信机制:全局`Context`和观察者模式。
关于 `Context`,这是我们设计的一个全局上下文,使用依赖注入的方式,将`Context`注入到各个插件模块,这样在插件模块中就能调用到`Context`上通用的方法了,我们扩充下 `Plugin` 类,使用我们内部实现的`IOC`,将全局 `Context` 注入进来。这样就实现了插件和核心的通信。
> 关于 `IOC` 的内容,参考我们的另一篇文章
```ts
export abstract class Plugin {
// 注入 Context
@Inject('Context')
protected _context: Context;
protected _name: string;
protected constructor(name: string) {
this._name = name;
}
getName(): string {
return this._name;
}
getContext(): Context {
return this._context;
}
abstract onMounted(ctx: Context): void;
abstract onDestroy(): void;
// 其它方法
}
```
关于观察者模式,我们实现了
1. `Observable` 类,提供基础的观察者模式功能如增加、移除、通知
2. `PathObservable` 类,包装单个 `Observable`,提供观察者名称
3. `ObserverManager` 管理类,和`PluginManager`类似,专门负责`PathObservable`实例的管理
然后在插件实例中,提供快捷注册观察者的方法 `addObserve`,同时提供获取其它插件的 API `getPluginByName`,这样就能监听其它插件的消息了。这样实现了插件之间的通信。
```ts
export abstract class Plugin<O = any> {
@Inject('Context')
protected _context: Context;
protected _name: string;
protected _observeNames: string[];
protected constructor(name: string) {
this._name = name;
this._observeNames = [];
}
getContext(): Context {
return this._context;
}
getName(): string {
return this._name;
}
abstract onMounted(ctx: Context): void;
abstract onMapping(container: IOCContainer): void;
onDestroy(): void {
this.removeObserve(...this._observeNames);
}
// 移除观察者
removeObserve(...names: string[]): void {
const manager = this._context.getObserverManager();
names.forEach((name) => {
manager.removeObserver(name, this._name);
});
}
// 获取其它插件实例
getPluginByName<T extends Plugin>(name: string): Nullable<T> {
return this._context.getPluginManager().getPluginByName<T>(name);
}
// 注册观察者
addObserve(...names: string[]): void {
const manager = this._context.getObserverManager();
names.forEach((name) => {
if (!this._observeNames.includes(name)) {
this._observeNames.push(name);
}
manager.addObserver(name, this._name, new Observable());
});
}
// 获取观察者
getObserver<T>(name: keyof O): Nullable<Observable<T>> {
const manager = this._context.getObserverManager();
return manager.getObserver<T, O>(name, this._name);
}
}
```
## 总结
以上就是我们在 UniverSheet 3.0 重构时候,关于插件化架构的设计和思考,项目还在不断优化和迭代,也有很多不足的地方,欢迎指出探讨。
最新源码请关注我们的官方 GitHub 仓库: <https://github.com/dream-num/UniverSheet>
## 参考
- [UniverSheet](https://github.com/dream-num/UniverSheet)
- [前端进阶:跟着开源项目学习插件化架构](https://zhuanlan.zhihu.com/p/149973342)
@@ -0,0 +1 @@
Implementation of front-end plug-in architecture in UniverSheet 3.0
@@ -0,0 +1,118 @@
# 前端 IOC 依赖注入在 UniverSheet 3.0 的实现
## 介绍
在软件工程中,依赖项注入是一种技术,通过它一个对象(或静态方法)可以提供另一个对象的依赖项。依赖项是可以使用的对象(服务)。
在理解它在编程中的含义之前,首先让我们了解一下它的总体含义,这可以帮助我们更好地理解这个概念。
依赖是指依靠某种东西来获得支持。比如我会说我们对手机的依赖程度过高。
在讨论依赖注入之前,我们先理解编程中的依赖是什么意思。
当 class A 使用 class B 的某些功能时,则表示 class A 具有 class B 依赖。
在 Java 中,在使用其他 class 的方法之前,我们首先需要创建那个 class 的对象(即 class A 需要创建一个 class B 实例)。
因此,将创建对象的任务转移给其他 class,并直接使用依赖项的过程,被称为“依赖项注入”。
## 需求
为什么需要使用依赖注入?
假设我们有一个 car class,其中包含各种对象,例如车轮、引擎等。
这里的 car class 负责创建所有依赖对象。现在,如果我们决定将来放弃 MRFWheels,而希望使用 Yokohama 车轮,该怎么办?
我们将需要使用新的 Yokohama 依赖关系来重新创建 car 对象。但是,当使用依赖注入(DI)时,我们可以在运行时更改车轮 wheels(因为可以在运行时而不是在编译时注入依赖项)。
你可以将依赖注入视为代码中的中间人,它负责创建想要的 wheels 对象,并将其提供给 car class。
它使 car class 不需要创建车轮 wheels、电池 battery 对象等。
## 设计
组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解 DI 的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
- 谁依赖于谁:当然是应用程序依赖于 IoC 容器;
- 为什么需要依赖:应用程序需要 IoC 容器来提供对象需要的外部资源;
- 谁注入谁:很明显是 IoC 容器注入应用程序里依赖的对象;
- 注入了什么:就是注入某个对象所需要的外部资源/依赖。
依赖注入明确描述了 “被注入对象依赖 IoC 容器配置依赖对象”,依赖注入是控制反转设计思想的一种实现。
## 原理
提供一个依赖管理的容器 “IOCContainer” 以及一个添加依赖项的方法 “addMapping”, 该方法接受 2 个参数 “依赖项的名称” 和 “依赖性项的 class 类型”,因为 javascript 不支持类型,所以依赖项的名称是必须的,指定依赖的名称也可以防止在 webpack 这类包管理工具中使用代码混淆,造成找不到类型的问题
```ts
class IOCContainer {
addMapping(name: string, type: Class) {
// ...
}
}
```
我们使用一个全局的 Map 管理所有的依赖项
```ts
class IOCContainer {
Map<string, Class>
}
```
getInstance 是我们从容器中获取依赖项的方法,它接受两个参数一个是依赖项的名称,另外一个是依赖项的作用域类型,这里我们提供 2 中作用域类型,“单例作用域” 以及 “多例作用域”
```ts
class IOCContainer {
getInstance(name: string, type: Scope) {
// ...
}
}
```
现在我们已经有了一个最简单的 IOC 容器,但是还存在一个问题现在容器并不知道依赖项之间的依赖关系,我们还需要一种机制这种机制可以让容器确定谁是依赖项谁是被依赖项
我们使用 @Inject 注解来确定依赖项之间的关联关系
```ts
function Inject(name: string): void {
// ...
}
```
@Inject 接收一个参数 name, 用来指定依赖项的名称。
### 示例
```ts
function bootstrap(): IOCContainer {
const container = new IOCContainer();
container.addMapping('Environment', Environment);
container.addMapping('Context', Context);
container.addMapping('UndoManager', UndoManager);
container.addMapping('UniverSheet', UniverSheet);
container.addMapping('PluginManager', PluginManager);
return container;
}
class Context {
@Inject('Environment')
private environment: Environment;
}
class UniverSheet {
@Inject('Context')
private context: Context;
static newInstance(): UniverSheet {
return bootstrap().getInstance('UniverSheet', Scope.prototype);
}
}
```
以上我们就完成了一个简单版本的 IOC 容器,下面我们分析一下这个简单的 IOC 容器还存在哪些能够优化的地方
## 优化
## 总结
## 参考
- [动手造轮子:实现一个简单的依赖注入](https://www.cnblogs.com/weihanli/p/implement-dependency-injection.html)
- [依赖注入是什么?如何使用它](https://chinese.freecodecamp.org/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it/)
- [什么是注入,什么时候使用它](https://blog.csdn.net/weixin_39932344/article/details/110813701)
@@ -0,0 +1,47 @@
# 使用 preact 创建组件库
## 介绍
### 什么是 preact
1. React 的 3kb 轻量化方案,可替代 react
2. Api 几乎和 react 一致
3. 提供兼容层 preact-compat,可以无缝使用 React 生态系统中其他组件
### 为什么用 preact
1. 出色的表现
[Preact](https://preactjs.com/) 速度很快,而不仅仅是因为它的大小。由于简单且可预测的 diff 实现,它是目前最快的虚拟 DOM 库之一。当涉及到性能时,会自动批量更新并优化 Preact。
<br/>
2. 小尺寸
大多数 UI 框架都足够大,足以占据应用程序 JavaScript 大小的大部分。Preact 有所不同:它很小,因此您的代码是应用程序中最大的部分。这意味着更少的 JavaScript 可以下载,解析和执行-为代码留出更多时间,因此您可以构建自己定义的体验,而无需努力保持框架的可控性。
<br/>
3. 便携式和嵌入式
Preact 的占地面积很小,这意味着您可以将强大的 Virtual DOM Component 范式带到其他地方无法企及的新地方。使用 Preact 无需复杂的集成即可构建应用程序的各个部分。将 Preact 嵌入到小部件中,并应用与构建完整应用程序相同的工具和技术。
<br/>
4. 兼容生态系统
通过虚拟 DOM 组件,可以轻松共享可重用的内容-从按钮到数据提供程序的所有内容。Preact 的设计意味着您可以无缝使用 React 生态系统中可用的数千个组件。在捆绑程序中添加一个简单的 preact / compat 别名即可提供一个兼容性层,该兼容性层甚至可以在您的应用程序中使用最复杂的 React 组件。
<br/>
## 需求
市面上的 UI 组件库虽然功能丰富,稳定性高,可以按需加载,但体积还是过大,并且风格固定。
UniverSheet 选用 preact 和 ts 开发 UI 组件库,创建一套拥有自己风格的轻量 UI 组件库,初步设计满足自己的需求,后续可以按照需求进行扩展。
## 开发
1. 创建组件目录
我们以 button 组件为例,为了样式可以按需引入,以及使用者可以方便修改样式,我们把 css 拿出来单独编写,当然如果你只是为了编写一个组件供少数人使用,也可以直接写在.tsx 里,一个组件包含以下文件:
```
├─Button
├─index.module.less //模块样式
|─button.tsx //组件文件
├─index.ts //单独打包文件
|─button.md //组件文档
└─test
├─index.test.tsx //测试组件
```
+42
View File
@@ -0,0 +1,42 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
parser: '@typescript-eslint/parser',
extends: ['airbnb-typescript', 'prettier', 'eslint:recommended', 'plugin:import/recommended', 'plugin:import/typescript'],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
// project: './tsconfig.json',
},
plugins: ['@typescript-eslint', 'prettier', 'import'],
rules: {
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'import/prefer-default-export': 'off',
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
// turn on errors for missing imports
'import/no-unresolved': 'error',
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
alwaysTryTypes: true, // always try to resolve config under `<root>@config` directory even it doesn't contain any source code, like `@config/unist`
// Choose from one of the "project" configs below or omit to use <root>/tsconfig.json by default
// use <root>/path/to/folder/tsconfig.json
project: './tsconfig.json',
},
},
},
};
+101
View File
@@ -0,0 +1,101 @@
#ide
*.DS_Store
.idea
.vscode
#npm
package-lock.json
dist
lib
docs/.vuepress/dist
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Runtime config
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL History
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
#lerna-changelog
.changelog
#editor workspace file
.idea/
.DS_Store
.history
# examples
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.env.local
.env.development.local
.env.test.local
.env.production.local
@@ -0,0 +1,7 @@
module.exports = {
singleQuote: true,
trailingComma: 'es5',
tabWidth: 4,
semi: true,
printWidth: 180,
};
+55
View File
@@ -0,0 +1,55 @@
# style-universheet
简体中文 | [English](./README.md)
## 介绍
UniverSheet Spreadsheet 插件
### 安装
```bash
npm i @univer/preact-ts
```
### 使用
```js
impport {Spreadsheet} from '@univer/preact-ts'
const univerSheet = new UniverSheet();
univerSheet.installPlugin(new Spreadsheet());
```
## 本地开发
### 环境
- [Node.js](https://nodejs.org/en/) Version >= 10
- [npm](https://www.npmjs.com/) Version >= 6
### 安装
```
pnpm install
```
### 开发
```
# 当前目录 ./packages/preact-ts/
npm run dev
# 或者项目根目录 ./
pnpm run --filter @univer/preact-ts dev
```
### 打包
```
# 当前目录 ./packages/preact-ts/
npm run build
# 或者根目录 ./
pnpm run --filter @univer/preact-ts build
```
+55
View File
@@ -0,0 +1,55 @@
# style-universheet
English| [简体中文](./README-zh.md)
## Introduction
UniverSheet Plugin Spreadsheet
### Installation
```bash
npm i @univer/preact-ts
```
### Usage
```js
impport {Spreadsheet} from '@univer/preact-ts'
const univerSheet = new UniverSheet();
univerSheet.installPlugin(new Spreadsheet());
```
## Local development
### Requirements
- [Node.js](https://nodejs.org/en/) Version >= 10
- [npm](https://www.npmjs.com/) Version >= 6
### Installation
```
pnpm install
```
### Development
```
# Current directory ./packages/preact-ts/
npm run dev
# Or project root directory ./
pnpm run --filter @univer/preact-ts dev
```
### Package
```
# Current directory ./packages/preact-ts/
npm run build
# Or root directory ./
pnpm run --filter @univer/preact-ts build
```
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>UniverSheet</title>
<style>
html,
body {
height: 100%;
}
</style>
</head>
<body>
<div id="app" style="z-index: 1; position: fixed; font-size: 20px; color: green;"></div>
<div id="universheet-demo-up" style="height: 100%;"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
moduleFileExtensions: ['js', 'ts', 'tsx'],
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
'^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207
},
globals: {
'ts-jest': {
tsConfig: 'tsconfig.test.json',
},
},
};
+67
View File
@@ -0,0 +1,67 @@
{
"name": "univer-preact-ts",
"version": "0.0.1",
"description": "UniverSheet normal preact-ts",
"keywords": [],
"author": "DreamNum <msoffice2@126.com>",
"license": "MIT",
"main": "./src/index.ts",
"module": "./src/index.ts",
"types": "./src/Types/index.d.ts",
"exports": {
".": "./src/index.ts",
"./*": "./src/*"
},
"publishConfig": {
"main": "./lib/univer-preact-ts.umd.js",
"module": "./lib/univer-preact-ts.es.js",
"types": "./lib/univer-preact-ts.d.ts"
},
"directories": {
"lib": "lib"
},
"scripts": {
"test": "jest",
"dev": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
"devDependencies": {
"@preact/preset-vite": "2.0.0",
"@testing-library/preact": "^2.0.1",
"@types/jest": "^27.5.2",
"@types/node": "^16.4.6",
"@vitejs/plugin-legacy": "^1.5.1",
"enzyme": "^3.11.0",
"enzyme-adapter-preact-pure": "^3.3.0",
"eslint": "^8.22.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"identity-obj-proxy": "^3.0.0",
"jest-dev-server": "^6.1.1",
"jest-environment-jsdom": "^29.0.3",
"less": "^4.1.1",
"prettier": "^2.3.2",
"ts-jest": "29.0.1",
"ts-node": "^10.2.1",
"typescript": "^4.4.1-rc",
"vite": "^2.4.4",
"vite-plugin-external": "1.2.3"
},
"dependencies": {
"@univer/base-component": "^0.0.1",
"@univer/base-render": "^0.0.1",
"@univer/core": "^0.0.1",
"@univer/sheets-plugin-alternating-colors": "^0.0.1",
"@univer/sheets-plugin-filter": "^0.0.1",
"@univer/base-sheets": "^0.0.1",
"@univer/style-universheet": "^0.0.1",
"csstype": "^3.0.11",
"preact": "^10.5.14"
}
}
+15
View File
@@ -0,0 +1,15 @@
/// <reference types="vite/client" />
export * from '../index';
declare module '@univer/custom-build' {}
import JSX = preact.JSX;
declare module 'es6-proxy-polyfill';
// declare module '*.less';
// use css module
declare module '*.less' {
const resource: { [key: string]: string };
export = resource;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './index';
declare module '@univer/style-universheet' {}
+35
View File
@@ -0,0 +1,35 @@
import { IStyleConfig } from '@univer/base-component';
import { IWorkbookConfig, UniverSheet } from '@univer/core';
import { FilterPlugin } from '@univer/sheets-plugin-filter';
import { SpreadsheetPlugin } from '@univer/base-sheets';
import { StyleUniver } from '@univer/style-universheet';
interface IPropsCustom {
workbookConfig?: Partial<IWorkbookConfig>;
spreadsheetConfig?: IStyleConfig;
}
/**
* Initialize the core and all plugins
*/
class UniverSheetCustom {
constructor() {}
init(config: IPropsCustom = {}): UniverSheet {
const univerSheetUp = UniverSheet.newInstance(config.workbookConfig);
univerSheetUp.installPlugin(new StyleUniver(config.spreadsheetConfig));
univerSheetUp.installPlugin(new SpreadsheetPlugin());
univerSheetUp.installPlugin(new FilterPlugin());
return univerSheetUp;
}
}
/**
* Wrapped into a function,easy to use
* @param config
* @returns
*/
const univerSheetCustom = function (config?: IPropsCustom) {
return new UniverSheetCustom().init(config);
};
export { univerSheetCustom };
+30
View File
@@ -0,0 +1,30 @@
import { univerSheetCustom } from '.';
const workbookConfig = {
id: '',
sheetOrder: [],
socketEnable: BooleanNumber.FALSE,
socketUrl: '',
name: '',
timeZone: '',
appVersion: '',
theme: '',
skin: '',
locale: '',
creator: '',
styles: [],
sheets: [],
lastModifiedBy: '',
createdTime: '',
modifiedTime: '',
};
const spreadsheetConfig = {
containerId: 'universheet-demo-up',
layout: 'auto',
};
const universheet = univerSheetCustom({
workbookConfig: workbookConfig,
spreadsheetConfig: spreadsheetConfig,
});
+1
View File
@@ -0,0 +1 @@
import JSX = preact.JSX;
+10
View File
@@ -0,0 +1,10 @@
declare module 'es6-proxy-polyfill';
// declare module '*.less';
// use css module
declare module '*.less' {
const resource: { [key: string]: string };
export = resource;
}
declare module 'enzyme';
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+31
View File
@@ -0,0 +1,31 @@
{
"compilerOptions": {
"rootDir": "./",
"outDir": "lib",
"target": "ESNext",
"lib": ["ESNext", "DOM", "WebWorker"],
"module": "ESNext",
"moduleResolution": "Node", //This value is needed for Node.js.
"declaration": true,
"declarationDir": "./lib",
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true,
"strict": true,
"allowJs": true,
"baseUrl": "./",
"skipLibCheck": true,
"esModuleInterop": true, // auto allowSyntheticDefaultImports tru
"suppressImplicitAnyIndexErrors": true,
"experimentalDecorators": true,
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
"strictPropertyInitialization": false,
"types": ["jest", "node", "@types/jest"]
},
"include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"]
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"composite": true
}
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
+52
View File
@@ -0,0 +1,52 @@
import { defineConfig } from 'vite';
import path from 'path';
import preact from '@preact/preset-vite';
import legacy from '@vitejs/plugin-legacy';
import { name, version } from './package.json';
import createExternal from 'vite-plugin-external';
const resolve = (url: string) => path.resolve(__dirname, url);
export default defineConfig({
build: {
lib: {
entry: resolve('src/index.ts'),
name: 'UniverSheetCustomBuild',
formats: ['es', 'umd'],
fileName: 'univer-custom-build',
},
outDir: './lib',
},
define: {
pkgJson: { name, version },
},
css: {
modules: {
localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor
generateScopedName: 'univer-[local]', // custom prefix class name
},
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
},
server: {
port: 3103,
open: true, // Automatically open the app in the browser on server start.
},
plugins: [
preact(),
// legacy({
// targets: ['ie >= 11'],
// additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
// }),
createExternal({
externals: {
// '@univer/core': '@univer/core',
// '@univer/style-universheet': '@univer/style-universheet',
// preact: 'preact',
},
}),
],
});
+51
View File
@@ -0,0 +1,51 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
parser: '@typescript-eslint/parser',
extends: [
'airbnb-typescript',
'prettier',
'eslint:recommended',
'plugin:import/recommended',
'plugin:import/typescript',
],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
project: './tsconfig.json',
},
plugins: ['@typescript-eslint', 'prettier', 'import'],
rules: {
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'import/prefer-default-export': 'off',
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
// turn on errors for missing imports
'import/no-unresolved': 'error',
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
alwaysTryTypes: true, // always try to resolve config under `<root>@config` directory even it doesn't contain any source code, like `@config/unist`
// Choose from one of the "project" configs below or omit to use <root>/tsconfig.json by default
// use <root>/path/to/folder/tsconfig.json
project: './tsconfig.json',
},
},
},
};
+101
View File
@@ -0,0 +1,101 @@
#ide
*.DS_Store
.idea
.vscode
#npm
package-lock.json
dist
lib
docs/.vuepress/dist
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Runtime config
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL History
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
#lerna-changelog
.changelog
#editor workspace file
.idea/
.DS_Store
.history
# examples
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.env.local
.env.development.local
.env.test.local
.env.production.local
@@ -0,0 +1,6 @@
module.exports = {
singleQuote: true,
trailingComma: 'es5',
tabWidth: 4,
semi: true,
};
+31
View File
@@ -0,0 +1,31 @@
# univer-vanilla-ts
简体中文 | [English](./README.md)
## 介绍
UniverSheet原生ts项目案例
## 用法
### 环境
- [Node.js](https://nodejs.org/en/) Version >= 10
### 安装
```
npm i
```
### 开发
```
npm run dev
```
### 打包
```
npm run build
```
+31
View File
@@ -0,0 +1,31 @@
# univer-vanilla-ts
English| [简体中文](./README-zh.md)
## Introduction
UniverSheet vanilla ts project case
## Usage
### Requirements
- [Node.js](https://nodejs.org/en/) Version >= 10
### Installation
```
npm i
```
### Development
```
npm run dev
```
### Package
```
npm run build
```
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>UniverSheet</title>
</head>
<body>
<div id="universheet-demo-up"></div>
<!-- <br />
<hr />
<br /> -->
<div id="universheet-demo-down"></div>
<div id='app'></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+47
View File
@@ -0,0 +1,47 @@
{
"name": "univer-vanilla-ts",
"version": "0.0.1",
"description": "UniverSheet vanilla ts demo project",
"keywords": [],
"author": "DreamNum <msoffice2@126.com>",
"license": "MIT",
"type": "module",
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1",
"dev": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
"devDependencies": {
"@preact/preset-vite": "^2.1.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.4.6",
"@vitejs/plugin-legacy": "^1.5.1",
"debug": "^4.3.3",
"enzyme": "^3.11.0",
"enzyme-adapter-preact-pure": "^3.3.0",
"eslint": "^8.22.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks":"^4.3.0",
"identity-obj-proxy": "^3.0.0",
"jest-dev-server": "^6.1.1",
"jest-environment-jsdom": "^29.0.3",
"less": "^4.1.1",
"prettier": "^2.3.2",
"typescript": "^4.4.1-rc",
"vite": "^2.4.4",
"vite-plugin-external": "1.2.3"
},
"dependencies": {
"@univer/base-component": "^0.0.1",
"@univer/core": "^0.0.1",
"@univer/style-universheet": "^0.0.1",
"csstype": "^3.0.11",
"preact": "^10.5.14"
}
}
+7
View File
@@ -0,0 +1,7 @@
export function App() {
return (
<>
<div></div>
</>
)
}
@@ -0,0 +1,10 @@
#universheet-demo-up {
width: 100%;
height: 50vh;
}
#universheet-demo-down {
width: 100%;
height: 40vh;
position: absolute;
bottom: 0;
}
+74
View File
@@ -0,0 +1,74 @@
// import { UniverSheet } from '@univer/core';
// import {
// ForceCalculationTypes,
// IDefaultFormat,
// IWorkbookData,
// IWorkbookProperties,
// RecalculationInterval,
// } from '@univer/core';
// import { StyleUniver } from '@univer/style-universheet';
// import './assets/css/style.less'
// const defaultDefaultFormat: IDefaultFormat = {};
// const defaultWorkbookProperties: IWorkbookProperties = {
// name: 'universheet', // File name
// creator: '', // Create user
// lastmodifiedby: '', // Edit user
// createdTime: '',
// modifiedTime: '',
// company: '梦数科技',
// appversion: '3.0.0', // Excel version, Is it necessary?
// Locale: 'zh',
// timeZone: 'UTC/GMT +8',
// defaultFormat: defaultDefaultFormat,
// forceCalculation: ForceCalculationTypes.ON,
// autoRecalculation: RecalculationInterval.HOUR,
// maxIterations: 1,
// };
// const defaultWorkbookData: IWorkbookData = {
// properties: defaultWorkbookProperties,
// data: [],
// pluginMetaData: {},
// userMetaData: {}, // 用户自定义数据
// spreadsheetUrl: '',
// spreadsheetKey: '',
// };
// const uiDefaultConfigUp = {
// containerId: 'universheet-demo-up',
// layout: 'auto'
// }
// const uiDefaultConfigDown = {
// containerId: 'universheet-demo-down',
// layout: {
// outerLeft: false,
// outerRight: true,
// innerLeft: false,
// innerRight: false,
// }
// }
// // univerSheetUp.installPlugin(new StyleUniver(uiDefaultConfigUp))
// // univerSheetDown.installPlugin(new StyleUniver(uiDefaultConfigDown))
// const univerSheetUp = new UniverSheet(defaultWorkbookData, {
// plugins: [
// new StyleUniver(uiDefaultConfigUp)
// ]
// });
// const univerSheetDown = new UniverSheet(defaultWorkbookData, {
// plugins: [
// new StyleUniver(uiDefaultConfigDown)
// ]
// });
import { render } from 'preact'
import { App } from './app'
render(<App />, document.getElementById('app')!)
+1
View File
@@ -0,0 +1 @@
import JSX = preact.JSX;
+8
View File
@@ -0,0 +1,8 @@
// declare module '*.less';
// use css module
declare module '*.less' {
const resource: { [key: string]: string };
export = resource;
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+27
View File
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"rootDir": "./",
"outDir": "lib",
"target": "ESNext",
"lib": ["ESNext", "DOM", "WebWorker"],
"module": "ESNext",
"moduleResolution": "Node", //This value is needed for Node.js.
"declaration": true,
"declarationDir": "./lib",
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true,
"strict": true,
"allowJs": true,
"baseUrl": "./",
"skipLibCheck": true,
"esModuleInterop": true, // auto allowSyntheticDefaultImports true
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
},
"include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"]
}
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"composite": true
}
}
+51
View File
@@ -0,0 +1,51 @@
import { defineConfig } from 'vite';
import path from 'path';
import legacy from '@vitejs/plugin-legacy';
import { name, version } from './package.json';
import preact from '@preact/preset-vite';
const resolve = (url: string) => path.resolve(__dirname, url);
export default defineConfig({
build: {
// lib: {
// entry: resolve('src/index.ts'),
// name: 'BaseWebUI',
// formats: ['es', 'umd'],
// fileName: 'index',
// },
// outDir: './lib',
},
define: {
pkgJson: { name, version },
},
esbuild: {
// jsxFactory: 'DOMcreateElement',
// jsxFragment: 'DOMcreateFragment',
// jsxInject: `import { DOMcreateElement, DOMcreateFragment } from '@/dom-helper/jsxFactory';`,
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
css: {
modules: {
localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor
},
},
server: {
port: 3101,
open: true, // Automatically open the app in the browser on server start.
},
plugins: [
preact(),
legacy({
targets: ['ie >= 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
}),
// babel(),
// babel({ babelHelpers: 'bundled' }),
],
});
+67
View File
@@ -0,0 +1,67 @@
{
"name": "univer",
"version": "1.0.0-pre-alpha",
"description": "Univer,Luckysheet 2.0 upgrade version",
"private": true,
"scripts": {
"test": "node scripts/test",
"test:ui": "pnpm run --filter @univer/style-universheet test",
"test:e2e": "pnpm run --filter @univer/base-sheets test:play",
"dev": "node scripts/dev",
"api": "node scripts/api",
"lighthouse-dev": "start /B node scripts/dev && lighthouse http://127.0.0.1:3103 --output html --output-path ./report.html",
"build": "node scripts/build.js",
"build:customd": "cd cli/src/templates/univer-custom-build && npm run build:d",
"clean": "npm cache clean --force && pnpm dlx rimraf node_modules **/node_modules/ package-lock.json yarn.lock pnpm-lock.yaml && pnpm install && pnpm dlx playwright install",
"node-server": "node scripts/server.js",
"univer-cli": "cd cli && npm run cli",
"postinstall": "node scripts/postinstall.js",
"prepare": "husky install",
"lint": "lint-staged"
},
"author": "DreamNum <msoffice2@126.com>",
"license": "MIT",
"devDependencies": {
"@changesets/cli": "^2.22.0",
"@microsoft/api-documenter": "^7.18.1",
"@microsoft/api-extractor": "7.25.0",
"@playwright/test": "^1.18.1",
"@types/jest": "28.1.8",
"@types/node": "^13.11.1",
"@types/numeral": "^2.0.2",
"cross-env": "^7.0.3",
"eslint": "8.22.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-plugin-import": "2.26.0",
"eslint-config-airbnb-typescript": "17.0.0",
"@typescript-eslint/eslint-plugin": "5.38.1",
"@typescript-eslint/parser": "5.38.1",
"eslint-plugin-unused-imports": "2.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.7.1",
"eslint-plugin-import-newlines": "1.2.3",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"execa": "^4.1.0",
"find-process": "^1.4.7",
"husky": "^8.0.1",
"jest": "29.0.0",
"lighthouse": "^9.4.0",
"lint-staged": "^13.0.3",
"nodemon": "^2.0.3",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"tree-kill": "^1.2.2",
"ts-jest": "29.0.1",
"ts-node": "^10.2.1",
"typescript": "^4.4.1-rc"
},
"lint-staged": {
"./packages/**/*.ts?(x)": [
"eslint --fix",
"git add"
]
}
}

Some files were not shown because too many files have changed in this diff Show More