chore: 迁移到 vite
@ -1,3 +0,0 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
@ -1,20 +1,15 @@
|
||||
// https://eslint.org/docs/rules/
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
'vue/setup-compiler-macros': true,
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/typescript/recommended',
|
||||
'@vue/eslint-config-typescript'
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
ecmaVersion: 'latest'
|
||||
},
|
||||
rules: {
|
||||
'curly': ['error', 'multi-line'],
|
||||
@ -64,9 +59,9 @@ module.exports = {
|
||||
'no-eval': 'error',
|
||||
'no-var': 'error',
|
||||
'no-with': 'error',
|
||||
'no-alert': isProduction ? 'error' : 'warn',
|
||||
'no-console': isProduction ? 'error' : 'warn',
|
||||
'no-debugger': isProduction ? 'error' : 'warn',
|
||||
'no-alert': 'warn',
|
||||
'no-console': 'warn',
|
||||
'no-debugger': 'error',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/ban-types': ['error', {
|
||||
'extendDefaults': true,
|
||||
@ -78,16 +73,5 @@ module.exports = {
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/*.{j,t}s?(x)',
|
||||
'**/tests/unit/**/*.spec.{j,t}s?(x)'
|
||||
],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
28
.gitignore
vendored
@ -1,22 +1,30 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
4
.husky/commit-msg
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint -e $HUSKY_GIT_PARAMS
|
@ -1,58 +0,0 @@
|
||||
// https://stylelint.io/user-guide/rules/list
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'stylelint-config-standard',
|
||||
'stylelint-config-html/html',
|
||||
'stylelint-config-html/vue',
|
||||
],
|
||||
rules: {
|
||||
'indentation': 2,
|
||||
'max-nesting-depth': 5,
|
||||
'string-no-newline': true,
|
||||
'string-quotes': 'single',
|
||||
'color-hex-case': 'lower',
|
||||
'color-hex-length': 'short',
|
||||
'color-no-invalid-hex': true,
|
||||
'font-weight-notation': 'numeric',
|
||||
'function-calc-no-unspaced-operator': true,
|
||||
'function-url-quotes': 'never',
|
||||
'block-no-empty': true,
|
||||
'no-eol-whitespace': true,
|
||||
'block-opening-brace-newline-after': 'always',
|
||||
'block-opening-brace-space-before': 'always',
|
||||
'declaration-block-no-duplicate-properties': [true, {
|
||||
ignoreProperties: ['overflow'],
|
||||
}],
|
||||
'declaration-block-semicolon-newline-after': 'always',
|
||||
'declaration-block-trailing-semicolon': 'always',
|
||||
'selector-pseudo-element-colon-notation': 'double',
|
||||
'declaration-block-no-redundant-longhand-properties': [true, {
|
||||
ignoreShorthands: ['inset'],
|
||||
}],
|
||||
'alpha-value-notation': 'number',
|
||||
|
||||
'max-empty-lines': null,
|
||||
'no-missing-end-of-source-newline': null,
|
||||
'no-descending-specificity': null,
|
||||
'at-rule-empty-line-before': null,
|
||||
'at-rule-no-unknown': null,
|
||||
'rule-empty-line-before': null,
|
||||
'number-leading-zero': null,
|
||||
'selector-pseudo-element-no-unknown': null,
|
||||
'selector-list-comma-newline-after': null,
|
||||
'no-invalid-double-slash-comments': null,
|
||||
'color-function-notation': null,
|
||||
'font-family-no-missing-generic-family-keyword': null,
|
||||
'selector-class-pattern': null,
|
||||
'custom-property-pattern': null,
|
||||
'keyframes-name-pattern': null,
|
||||
'shorthand-property-no-redundant-values': null,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.scss', '**/*.scss'],
|
||||
customSyntax: 'postcss-scss'
|
||||
},
|
||||
],
|
||||
}
|
7
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
51
README.md
@ -22,7 +22,7 @@
|
||||
|
||||
|
||||
# 🎨 PPTist
|
||||
> 一个基于 Vue3.x + TypeScript 的在线演示文稿(幻灯片)应用,还原了大部分 Office PowerPoint 常用功能,支持 文字、图片、形状、线条、图表、表格、视频、音频、公式 几种最常用的元素类型,每一种元素都拥有高度可编辑能力,同时支持丰富的快捷键和右键菜单,力求还原桌面应用级体验。支持导出本地 PPTX 文件,支持移动端基础编辑和预览,支持 PWA。您可以在此基础上搭建自己的在线幻灯片应用。
|
||||
> 一个基于 Vue3.x + TypeScript 的在线演示文稿(幻灯片)应用,还原了大部分 Office PowerPoint 常用功能,支持 文字、图片、形状、线条、图表、表格、视频、音频、公式 几种最常用的元素类型,每一种元素都拥有高度可编辑能力,同时支持丰富的快捷键和右键菜单,力求还原桌面应用级体验。支持导出本地 PPTX 文件,支持移动端基础编辑和预览。您可以在此基础上搭建自己的在线幻灯片应用。
|
||||
|
||||
<b>在线体验地址👉:[https://pipipi-pikachu.github.io/PPTist/](https://pipipi-pikachu.github.io/PPTist/)</b>
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
```
|
||||
npm install
|
||||
|
||||
npm run serve
|
||||
npm run dev
|
||||
```
|
||||
|
||||
|
||||
@ -166,18 +166,6 @@ npm run serve
|
||||
- 播放预览
|
||||
|
||||
|
||||
# 📅 后续重点规划
|
||||
- 项目工程升级:
|
||||
- 升级 TypeScript v5.x;
|
||||
- 将 npm 更换到 pnpm;
|
||||
- 将 Vue CLI 更换到 Vite 生态;
|
||||
- 支持 Iframe 引用;
|
||||
- 组合元素重构:能够支持组合元素进行旋转、缩放、整体执行动画等;
|
||||
- 支持多屏放映;
|
||||
- 导出HTML文件;
|
||||
- 补充注释/文档;
|
||||
- 导入PPTX持续优化;
|
||||
|
||||
# 🎯 开发
|
||||
目前没有完整的开发文档,但下面这些文档可能会对你有一些帮助:
|
||||
- [项目目录与数据结构](https://github.com/pipipi-pikachu/PPTist/blob/master/doc/DirectoryAndData.md)
|
||||
@ -185,36 +173,17 @@ npm run serve
|
||||
- [如何自定义一个元素](https://github.com/pipipi-pikachu/PPTist/blob/master/doc/CustomElement.md)
|
||||
|
||||
|
||||
# 💻 贡献代码
|
||||
首先感谢关注本项目的朋友,非常欢迎每一位对本项目感兴趣的朋友贡献代码。
|
||||
|
||||
### 具体参考如下:
|
||||
- fork 源码,下载到本地并运行项目
|
||||
- 添加/修改代码
|
||||
- <b>对相关改动进行全面的自我测试(这非常重要,否则作者对提交代码进行阅读、测试、修复的成本很可能远大于自己重新实现)</b>
|
||||
- 确认无误后提交修改到 Github
|
||||
- 提交 Pull Request
|
||||
|
||||
### 另外需要注意的是:
|
||||
- 每一次 Pull Request 都不应该提交过多的代码,且务必说明本次改动的具体目的,例如:修复了某个 bug、优化了某个方法 等,方便进行 Code Review;
|
||||
- 对于 bug 的修复,应该将本次 Pull Request 和相对应 bug 的 issue 关联起来,让别人知道该问题已经被修复;
|
||||
- 对于较大的新功能,你需要先提交 Issues,例如 “添加 XXX 功能”,确认该功能有被添加的必要后,再开始工作;
|
||||
- 对于一些主观的样式、交互逻辑调整:如颜色、图标的使用,某些预设配置的增减修改等,一般不予通过。但可以在 Issues 中进行讨论;
|
||||
- 其他如简单的代码优化、文档修正等,只要修改合理都会被接受。
|
||||
|
||||
在此感谢每一位贡献者。
|
||||
|
||||
|
||||
# 📄 版权声明/开源协议
|
||||
[AGPL-3.0 License](https://github.com/pipipi-pikachu/PPTist/blob/master/LICENSE) | Copyright © 2020-PRESENT [pipipi-pikachu](https://github.com/pipipi-pikachu)
|
||||
|
||||
# 🧮 商业用途
|
||||
- 如果你希望将本项目商用盈利,我希望你能严格遵循 AGPL-3.0 协议,回馈开源社区;
|
||||
- 此外,如果你真的需要闭源商用,无法执行 AGPL-3.0 协议,可以选择:
|
||||
1. 使用 [Apache 2.0 LICENSE 版本](https://github.com/pipipi-pikachu/PPTist/archive/f1a35bb8e045124e37dcafd6acbf40b4531b69aa.zip)(建议);
|
||||
- 如果你希望将本项目商用盈利,我希望你能尊重开源,严格遵循 AGPL-3.0 协议,回馈开源社区;
|
||||
- 如果你因为任何原因,必须要闭源商用,无法执行 AGPL-3.0 协议,可以选择:
|
||||
1. 使用早期的 [Apache 2.0 LICENSE 版本](https://github.com/pipipi-pikachu/PPTist/archive/f1a35bb8e045124e37dcafd6acbf40b4531b69aa.zip)(建议);
|
||||
2. 成为项目的贡献者,大致包括:
|
||||
- 你的代码被本项目作为依赖引用;
|
||||
- 你提交的 PR 被本项目合并(仅限有价值的,不包括简单的错别字或拼写错误修改等);
|
||||
- 你参与过本项目的设计、实现(也包括对各种功能/模块的实现或Bug的修复提供了有价值的思路);
|
||||
- 注:先违反协议后再成为贡献者不在此范围;
|
||||
3. 邮件联系作者付费商用(不建议,我更希望你一起加入到开源社区中来);
|
||||
- 你给本项目提交过重要的 PR 并且被合并;
|
||||
- 你参与过本项目的设计,也包括对各种功能/模块的实现或Bug的修复提供了有价值的思路;
|
||||
- 注1:满足条件的单位在商用前请先联系作者确认资格,避免误会。
|
||||
- 注2:先违反协议后再成为贡献者不在此项范围;
|
||||
3. 邮件联系作者付费商用(如果可以,我更希望你一起加入到开源社区中来);
|
@ -1,16 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset',
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
'import',
|
||||
{
|
||||
libraryName: '@icon-park/vue-next',
|
||||
libraryDirectory: 'es/icons',
|
||||
camel2DashComponentName: false,
|
||||
},
|
||||
'iconPark',
|
||||
],
|
||||
],
|
||||
}
|
@ -10,9 +10,7 @@
|
||||
* style 代码格式修改
|
||||
* test 测试用例修改
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@commitlint/config-conventional',
|
||||
],
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
}
|
2
env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="vite/client" />
|
@ -1,13 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="基于 Vue3.x + TypeScript 的在线演示文稿(幻灯片)应用,还原了大部分 Office PowerPoint 常用功能,实现在线PPT的编辑、演示。支持导出PPT文件。" />
|
||||
<meta name="keywords" content="ppt,powerpoint,office powerpoint,在线ppt,幻灯片,演示文稿,ppt在线制作,Vue3,TypeScript" />
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>PPTIST - 在线演示文稿</title>
|
||||
|
||||
<style>
|
||||
@ -47,18 +47,16 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app">
|
||||
<div class="first-screen-loading">
|
||||
<div class="first-screen-loading-spinner"></div>
|
||||
<div class="first-screen-loading-text">正在加载中,请稍等 ...</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
document.oncontextmenu = e => e.preventDefault()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
23472
package-lock.json
generated
102
package.json
@ -1,85 +1,73 @@
|
||||
{
|
||||
"name": "pptist",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"build:fonts": "node scripts/build-fonts"
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@icon-park/vue-next": "^1.4.2",
|
||||
"animate.css": "^4.1.1",
|
||||
"chartist": "^1.3.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"core-js": "^3.8.3",
|
||||
"crypto-js": "^4.0.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dexie": "3.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"hfmath": "0.0.2",
|
||||
"hfmath": "^0.0.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"nanoid": "^4.0.2",
|
||||
"nanoid": "^5.0.4",
|
||||
"number-precision": "^1.6.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pptxgenjs": "^3.12.0",
|
||||
"pptxtojson": "^0.1.2",
|
||||
"prosemirror-commands": "^1.3.0",
|
||||
"prosemirror-dropcursor": "^1.6.0",
|
||||
"prosemirror-gapcursor": "^1.3.1",
|
||||
"prosemirror-history": "^1.3.0",
|
||||
"prosemirror-inputrules": "^1.2.0",
|
||||
"prosemirror-keymap": "^1.2.0",
|
||||
"prosemirror-model": "^1.18.1",
|
||||
"prosemirror-schema-basic": "^1.2.0",
|
||||
"prosemirror-schema-list": "^1.2.1",
|
||||
"prosemirror-state": "^1.4.1",
|
||||
"prosemirror-view": "^1.27.2",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"prosemirror-commands": "^1.5.2",
|
||||
"prosemirror-dropcursor": "^1.8.1",
|
||||
"prosemirror-gapcursor": "^1.3.2",
|
||||
"prosemirror-history": "^1.3.2",
|
||||
"prosemirror-inputrules": "^1.3.0",
|
||||
"prosemirror-keymap": "^1.2.2",
|
||||
"prosemirror-model": "^1.19.4",
|
||||
"prosemirror-schema-basic": "^1.2.2",
|
||||
"prosemirror-schema-list": "^1.3.0",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-view": "^1.32.7",
|
||||
"svg-arc-to-cubic-bezier": "^3.2.0",
|
||||
"svg-pathdata": "^6.0.3",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"vue": "^3.3.7",
|
||||
"vue": "^3.3.11",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.6.6",
|
||||
"@commitlint/config-conventional": "^17.6.6",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/resize-observer-browser": "^0.1.4",
|
||||
"@types/svg-arc-to-cubic-bezier": "^3.2.0",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-plugin-pwa": "~5.0.0",
|
||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"@vue/compiler-sfc": "^3.3.7",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"babel-plugin-import": "^1.13.3",
|
||||
"eslint": "^8.34.0",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"husky": "8.0.3",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"sass": "^1.32.13",
|
||||
"sass-loader": "^8.0.2",
|
||||
"stylelint": "^15.10.2",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-webpack-plugin": "^4.1.1",
|
||||
"typescript": "~4.7.4"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
"@commitlint/cli": "^18.4.3",
|
||||
"@commitlint/config-conventional": "^18.4.3",
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@tsconfig/node18": "^18.2.2",
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/node": "^18.19.3",
|
||||
"@types/svg-arc-to-cubic-bezier": "^3.2.2",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/tsconfig": "^0.5.0",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"husky": "^8.0.3",
|
||||
"npm-run-all2": "^6.1.1",
|
||||
"sass": "^1.69.6",
|
||||
"typescript": "~5.3.0",
|
||||
"vite": "^5.0.10",
|
||||
"vue-tsc": "^1.8.25"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 1.1 KiB |
@ -1,45 +0,0 @@
|
||||
// 自动获取 src\assets\fonts 路径下的字体文件列表,并替换相关的 SCSS 变量
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const assetsPath = path.join(__dirname, '../src/assets')
|
||||
const fontVarPath = path.join(assetsPath, 'styles/font.scss')
|
||||
const fontsPath = path.join(assetsPath, 'fonts')
|
||||
|
||||
const scssFontListVar = '$fontList'
|
||||
|
||||
const replaceSCSSVariable = (names) => {
|
||||
fs.readFile(fontVarPath, 'utf-8', (errs, content) => {
|
||||
if (errs) {
|
||||
console.error(errs)
|
||||
process.exit(1)
|
||||
}
|
||||
const reg = new RegExp(`(\\${scssFontListVar}:\\s*)(.*)(;.*)`, 'g')
|
||||
const newContent = content.replace(reg, `$1${names}$3`)
|
||||
fs.writeFile(fontVarPath, newContent, errs => {
|
||||
if (errs) {
|
||||
console.error(errs)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('自动生成自定义字体列表完成')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fs.readdir(fontsPath, { withFileTypes: true }, (errs, files) => {
|
||||
if (errs) {
|
||||
console.error(errs)
|
||||
process.exit(1)
|
||||
}
|
||||
const woff2Fonts = files.filter(({ name }) => name.endsWith('.woff2'))
|
||||
const fontList = woff2Fonts.map(({ name }) => name.replace('.woff2', ''))
|
||||
|
||||
const names = fontList.reduce((result, name, i) => {
|
||||
if (i === 0) return `'${name}'`
|
||||
return `${result}, '${name}'`
|
||||
}, '')
|
||||
replaceSCSSVariable(names)
|
||||
})
|
@ -4,6 +4,8 @@
|
||||
<Mobile v-else />
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
@ -23,7 +25,7 @@ const snapshotStore = useSnapshotStore()
|
||||
const { databaseId } = storeToRefs(mainStore)
|
||||
const { screening } = storeToRefs(useScreenStore())
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (import.meta.env.MODE !== 'development') {
|
||||
window.onbeforeunload = () => false
|
||||
}
|
||||
|
||||
|
@ -355,7 +355,7 @@ export default () => {
|
||||
if (!cell.text) return cell
|
||||
return {
|
||||
...cell,
|
||||
text: cell.text.replaceAll(searchWord.value, replaceWord.value),
|
||||
text: cell.text.replace(new RegExp(searchWord.value, 'g'), replaceWord.value),
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import './registerServiceWorker'
|
||||
|
||||
import '@icon-park/vue-next/styles/index.css'
|
||||
import 'prosemirror-view/style/prosemirror.css'
|
||||
import 'animate.css'
|
||||
|
||||
import '@/assets/styles/prosemirror.scss'
|
||||
import '@/assets/styles/global.scss'
|
||||
import '@/assets/styles/font.scss'
|
||||
@ -15,9 +13,7 @@ import Icon from '@/plugins/icon'
|
||||
import Directive from '@/plugins/directive'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(Icon)
|
||||
app.use(Directive)
|
||||
|
||||
app.use(createPinia())
|
||||
app.mount('#app')
|
||||
|
@ -1,32 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from 'register-service-worker'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready() {
|
||||
console.log(
|
||||
'App is being served from cache by a service worker.\n' +
|
||||
'For more details, visit https://goo.gl/AFskqB'
|
||||
)
|
||||
},
|
||||
registered() {
|
||||
console.log('Service worker has been registered.')
|
||||
},
|
||||
cached() {
|
||||
console.log('Content has been cached for offline use.')
|
||||
},
|
||||
updatefound() {
|
||||
console.log('New content is downloading.')
|
||||
},
|
||||
updated() {
|
||||
console.log('New content is available; please refresh.')
|
||||
},
|
||||
offline() {
|
||||
console.log('No internet connection found. App is running in offline mode.')
|
||||
},
|
||||
error(error) {
|
||||
console.error('Error during service worker registration:', error)
|
||||
}
|
||||
})
|
||||
}
|
7
src/shims-vue.d.ts
vendored
@ -1,7 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
@ -96,11 +96,11 @@ export const useSlidesStore = defineStore('slides', {
|
||||
const subColor = tinycolor(fontColor).isDark() ? 'rgba(230, 230, 230, 0.5)' : 'rgba(180, 180, 180, 0.5)'
|
||||
|
||||
const layoutsString = JSON.stringify(layouts)
|
||||
.replaceAll('{{themeColor}}', themeColor)
|
||||
.replaceAll('{{fontColor}}', fontColor)
|
||||
.replaceAll('{{fontName}}', fontName)
|
||||
.replaceAll('{{backgroundColor}}', backgroundColor)
|
||||
.replaceAll('{{subColor}}', subColor)
|
||||
.replace(/{{themeColor}}/g, themeColor)
|
||||
.replace(/{{fontColor}}/g, fontColor)
|
||||
.replace(/{{fontName}}/g, fontName)
|
||||
.replace(/{{backgroundColor}}/g, backgroundColor)
|
||||
.replace(/{{subColor}}/g, subColor)
|
||||
|
||||
return JSON.parse(layoutsString)
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import { lexer } from './lexer'
|
||||
import { parser } from './parser'
|
||||
import { format } from './format'
|
||||
import { toHTML } from './stringify'
|
||||
import type { AST } from './types'
|
||||
export type { AST } from './types'
|
||||
|
||||
export const toAST = (str: string) => {
|
||||
const tokens = lexer(str)
|
||||
@ -12,4 +12,4 @@ export const toAST = (str: string) => {
|
||||
return format(nodes)
|
||||
}
|
||||
|
||||
export { toHTML, AST }
|
||||
export { toHTML }
|
||||
|
@ -1,6 +1,98 @@
|
||||
// @ts-ignore
|
||||
import { SVGPathData } from 'svg-pathdata'
|
||||
import arcToBezier from 'svg-arc-to-cubic-bezier'
|
||||
|
||||
type CommandM = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.MOVE_TO
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
type CommandL = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.LINE_TO
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
type CommandH = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.HORIZ_LINE_TO
|
||||
x: number
|
||||
}
|
||||
type CommandV = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.VERT_LINE_TO
|
||||
y: number
|
||||
}
|
||||
type CommandZ = {
|
||||
type: typeof SVGPathData.CLOSE_PATH
|
||||
}
|
||||
type CommandQ = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.QUAD_TO
|
||||
x1: number
|
||||
y1: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
type CommandT = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.SMOOTH_QUAD_TO
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
type CommandC = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.CURVE_TO
|
||||
x1: number
|
||||
y1: number
|
||||
x2: number
|
||||
y2: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
type CommandS = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.SMOOTH_CURVE_TO
|
||||
x2: number
|
||||
y2: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
type CommandA = {
|
||||
relative: boolean
|
||||
type: typeof SVGPathData.ARC
|
||||
rX: number
|
||||
rY: number
|
||||
xRot: number
|
||||
sweepFlag: 0 | 1
|
||||
lArcFlag: 0 | 1
|
||||
x: number
|
||||
y: number
|
||||
cX?: number
|
||||
cY?: number
|
||||
phi1?: number
|
||||
phi2?: number
|
||||
}
|
||||
type SVGCommand = CommandM | CommandL | CommandH | CommandV | CommandZ | CommandQ | CommandT | CommandC | CommandS | CommandA
|
||||
|
||||
declare class SVGPathData {
|
||||
commands: SVGCommand[]
|
||||
constructor(content: string | SVGCommand[])
|
||||
static readonly CLOSE_PATH: 1
|
||||
static readonly MOVE_TO: 2
|
||||
static readonly HORIZ_LINE_TO: 4
|
||||
static readonly VERT_LINE_TO: 8
|
||||
static readonly LINE_TO: 16
|
||||
static readonly CURVE_TO: 32
|
||||
static readonly SMOOTH_CURVE_TO: 64
|
||||
static readonly QUAD_TO: 128
|
||||
static readonly SMOOTH_QUAD_TO: 256
|
||||
static readonly ARC: 512
|
||||
static readonly LINE_COMMANDS: number
|
||||
static readonly DRAWING_COMMANDS: number
|
||||
}
|
||||
|
||||
const typeMap = {
|
||||
1: 'Z',
|
||||
2: 'M',
|
||||
|
@ -57,7 +57,7 @@
|
||||
<div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
|
||||
<IconDownload class="icon" />
|
||||
</div>
|
||||
<a class="github-link" href="https://github.com/pipipi-pikachu/PPTist" target="_blank">
|
||||
<a class="github-link" v-tooltip="'Copyright © 2020-PRESENT pipipi-pikachu'" href="https://github.com/pipipi-pikachu/PPTist" target="_blank">
|
||||
<div class="menu-item"><IconGithub class="icon" /></div>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -170,7 +170,7 @@ const updateText = (content: string) => {
|
||||
const checkEmptyText = () => {
|
||||
if (!props.elementInfo.text) return
|
||||
|
||||
const pureText = props.elementInfo.text.content.replaceAll(/<[^>]+>/g, '')
|
||||
const pureText = props.elementInfo.text.content.replace(/<[^>]+>/g, '')
|
||||
if (!pureText) {
|
||||
slidesStore.removeElementProps({ id: props.elementInfo.id, propName: 'text' })
|
||||
addHistorySnapshot()
|
||||
|
@ -167,7 +167,7 @@ const updateContent = (content: string) => {
|
||||
}
|
||||
|
||||
const checkEmptyText = debounce(function() {
|
||||
const pureText = props.elementInfo.content.replaceAll(/<[^>]+>/g, '')
|
||||
const pureText = props.elementInfo.content.replace(/<[^>]+>/g, '')
|
||||
if (!pureText) slidesStore.deleteElement(props.elementInfo.id)
|
||||
}, 300, { trailing: true })
|
||||
|
||||
|
13
tsconfig.app.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env",
|
||||
"resize-observer-browser"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
17
tsconfig.node.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "@tsconfig/node18/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
26
vite.config.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `
|
||||
@import '@/assets/styles/variable.scss';
|
||||
@import '@/assets/styles/mixin.scss';
|
||||
`
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
})
|
@ -1,85 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const StyleLintPlugin = require('stylelint-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
publicPath: './',
|
||||
css: {
|
||||
loaderOptions: {
|
||||
sass: {
|
||||
prependData: `
|
||||
@import '~@/assets/styles/variable.scss';
|
||||
@import '~@/assets/styles/mixin.scss';
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
configureWebpack: {
|
||||
plugins: [
|
||||
new StyleLintPlugin({
|
||||
files: ['src/**/*.{vue,html,css,scss}'],
|
||||
failOnError: false,
|
||||
cache: false,
|
||||
fix: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
pwa: {
|
||||
name: 'PPTist',
|
||||
themeColor: '#d14424',
|
||||
iconPaths: {
|
||||
faviconSVG: null,
|
||||
favicon32: 'icons/favicon-32x32.png',
|
||||
favicon16: 'icons/favicon-16x16.png',
|
||||
appleTouchIcon: 'icons/apple-touch-icon-152x152.png',
|
||||
maskIcon: null,
|
||||
msTileImage: null,
|
||||
},
|
||||
manifestOptions: {
|
||||
name: 'PPTist',
|
||||
short_name: 'PPTist',
|
||||
theme_color: '#d14424',
|
||||
icons: [{
|
||||
src: 'icons/android-chrome-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png'
|
||||
}, {
|
||||
src: 'icons/android-chrome-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png'
|
||||
}, {
|
||||
src: 'icons/android-chrome-maskable-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'maskable'
|
||||
}, {
|
||||
src: 'icons/android-chrome-maskable-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'maskable'
|
||||
}],
|
||||
start_url: '.',
|
||||
display: 'standalone',
|
||||
background_color: '#000000',
|
||||
},
|
||||
workboxOptions: {
|
||||
runtimeCaching: [{
|
||||
urlPattern: /.*/,
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'PPTist',
|
||||
expiration: {
|
||||
maxAgeSeconds: 60 * 60 * 10,
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
}],
|
||||
include: [
|
||||
/\.ttf$/,
|
||||
],
|
||||
skipWaiting: true,
|
||||
}
|
||||
},
|
||||
}
|