Compare commits

..

No commits in common. "master" and "dev" have entirely different histories.
master ... dev

328 changed files with 4883 additions and 19479 deletions

View File

@ -2,8 +2,3 @@ node_modules
.git
dist
.history
images
docs
Dockerfile
README.md
build.sh

View File

@ -16,7 +16,9 @@ module.exports = {
],
globals: {
"NODE_ENV": false,
"VERSION": false
"VERSION": false,
"GOOGLE_CLIENT_ID": false,
"GITHUB_CLIENT_ID": false
},
// check if imports actually resolve
'settings': {

1
.gitignore vendored
View File

@ -2,7 +2,6 @@
node_modules/
dist/
.history
.idea
npm-debug.log*
.vscode
stackedit_v4

View File

@ -1,22 +1,16 @@
language: node_js
node_js:
- "12"
- "8"
services:
- docker
before_deploy:
# Run docker build
- docker build -t benweet/stackedit .
# Install Helm
- curl -SL -o /tmp/get_helm.sh https://git.io/get_helm.sh
- chmod 700 /tmp/get_helm.sh
- /tmp/get_helm.sh
- helm init --client-only
deploy:
provider: script
script: bash build/deploy.sh
script: bash build/docker-push-tag.sh
on:
tags: true

View File

@ -1,10 +1,21 @@
FROM mafgwo/wkhtmltopdf-nodejs:11.15.0
FROM benweet/stackedit-base
RUN mkdir -p /opt/stackedit/stackedit_v4
WORKDIR /opt/stackedit/stackedit_v4
ENV SERVE_V4 true
ENV V4_VERSION 4.3.22
RUN npm pack stackedit@$V4_VERSION \
&& tar xzf stackedit-*.tgz --strip 1 \
&& yarn \
&& yarn cache clean \
&& rm -rf ~/.cache/bower \
&& rm -rf ~/.local/share/bower
WORKDIR /opt/stackedit
COPY package*json /opt/stackedit/
COPY gulpfile.js /opt/stackedit/
RUN npm install --unsafe-perm \
&& npm cache clean --force
COPY . /opt/stackedit

209
README.md
View File

@ -1,183 +1,22 @@
<h1 align="center" style="text-align:center;">
<img src="chrome-app/icon-512.png" width="128" />
<br />
StackEdit中文版
</h1>
<p align="center">
<strong>笔记利器在线Markdown编辑器。</strong><br>
项目clone自<a href="https://gitee.com/mafgwo/stackedit" target="_blank" title="豆萁">豆萁/stackedit</a>如果你喜欢该项目请过去点一下Star您的肯定是作者最大的动力
</p>
<p align="center">
<a href="https://stackedit.cn/">https://stackedit.cn</a>
</p>
<p align="center">
<a target="_blank" href="https://www.apache.org/licenses/LICENSE-2.0.txt">
<img src="https://img.shields.io/:license-Apache2-blue.svg" alt="Apache 2" />
</a>
<a target="_blank" href="https://hub.docker.com/r/mafgwo/stackedit">
<img src="https://img.shields.io/docker/pulls/mafgwo/stackedit.svg" alt="Docker Pulls" />
</a>
<a target="_blank" href='https://gitee.com/mafgwo/stackedit/stargazers'>
<img src='https://gitee.com/mafgwo/stackedit/badge/star.svg' alt='gitee star'/>
</a>
</p>
<br/>
<hr />
1 笔记支持Gitee、GitHub、Gitea等Git仓库存储。<br>
2 支持直接上传图片也支持多种外部图床GitHub、Gitea、SM.MS、自定义图床粘贴或拖拽上传。<br>
3 编辑区域支持选择主题或自定义,总有你喜欢的主题。<br>
4 支持历史版本管理,不用担心编辑覆盖后无法回滚。<br>
5 支持ChatGPT辅助写作。<br>
6 支持KaTeX数学表达式、Mermaid UML图、乐谱等扩展。
<hr />
# StackEdit
## 说明
[![Build Status](https://img.shields.io/travis/benweet/stackedit.svg?style=flat)](https://travis-ci.org/benweet/stackedit) [![NPM version](https://img.shields.io/npm/v/stackedit.svg?style=flat)](https://www.npmjs.org/package/stackedit)
本项目为本人clone修改自用如果你也喜欢请至原作者处获取及交流。
> Full-featured, open-source Markdown editor based on PageDown, the Markdown library used by Stack Overflow and the other Stack Exchange sites.
## 截图
https://stackedit.io/
**亮暗主题切换、编辑主题切换**
![](./images/theme.gif)
### Ecosystem
**支持的文档空间**
![](./images/workspace.png)
- [Chrome app](https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg)
- NEW! Embed StackEdit in any website with [stackedit.js](https://github.com/benweet/stackedit.js)
- NEW! [Chrome extension](https://chrome.google.com/webstore/detail/ajehldoplanpchfokmeempkekhnhmoha) that uses stackedit.js
- [Community](https://community.stackedit.io/)
**拖拽粘贴上传图片**
![](./images/uploadimg.gif)
### Build Setup
**支持文档搜索**
![](./images/search.gif)
**ChatGPT集成协助写作**
![](./images/chatgpt.gif)
## 相比国外开源版本的区别:
- 修复了Github授权登录问题
- 支持了Gitee仓库2022-05-25
- 支持了Gitea仓库2022-05-25
- 汉化2022-06-01
- 主文档空间从GoogleDrive切换为Gitee2022-06-04
- 支持SM.MS图床粘贴/拖拽图片自动上传2022-07-01
- 支持Gitea图床粘贴/拖拽图片自动上传2022-07-02
- 支持自定义图床粘贴/拖拽图片自动上传2022-07-04
- 支持GitHub图床粘贴/拖拽图片自动上传2022-07-31
- 支持了右上角一键切换主题补全了深色主题的样式2022-08-07
- 编辑与预览区域样式优化2022-08-10
- 左边栏文件资源管理支持搜索文件2022-08-17
- 支持[TOC]目录2022-09-04
- 发布支持填写提交信息[针对Gitee、GitHub、Gitea、Gitlab]2022-09-10
- 支持文档空间关闭自动同步[针对Gitee、GitHub、Gitea、Gitlab]关闭后可自定义提交信息2022-09-23
- Gitea支持后端配置指定应用ID和Secret2022-10-03
- 支持编辑区域选择主题样式2022-10-06
- 支持图片直接存储到当前文档空间2022-10-29
- 支持MD文档之间链接跳转2022-11-20
- 支持预览区域选择主题样式2022-12-04
- Gitlab的支持优化2023-02-23
- 导出HTML、PDF支持带预览主题导出2023-02-26
- 支持分享文档2023-03-30
- 支持ChatGPT生成内容2023-04-10
- GitLab授权接口调整2023-08-26
- 主文档空间支持GitHub登录2023-10-19
## 国外开源版本弊端:
- 作者已经不维护了或很少维护了
- 不支持国内常用Gitee
- 强依赖GoogleDrive而Google Drive在国内不能正常访问
## 部署说明
> 建议docker-compose方式部署其他部署方式如遇到问题欢迎提issue。
docker官方仓库下载太慢可以使用阿里云的镜像仓库镜像仓库地址registry.cn-hangzhou.aliyuncs.com/mafgwo/stackedit:【版本号】
`docker-compose.yml`如下:
```yaml
version: "3.7"
services:
stackedit:
image: mafgwo/stackedit:【docker中央仓库找到最新版本】
container_name: stackedit
environment:
- LISTENING_PORT=8080
- ROOT_URL=/
- USER_BUCKET_NAME=root
- DROPBOX_APP_KEY=【不需要支持则删掉】
- DROPBOX_APP_KEY_FULL=【不需要支持则删掉】
- GITHUB_CLIENT_ID=【不需要支持则删掉】
- GITHUB_CLIENT_SECRET=【不需要支持则删掉】
- GITEE_CLIENT_ID=【不需要支持则删掉】
- GITEE_CLIENT_SECRET=【不需要支持则删掉】
- GOOGLE_CLIENT_ID=【不需要支持则删掉】
- GOOGLE_API_KEY=【不需要支持则删掉】
- GITEA_CLIENT_ID=【不需要支持则删掉】
- GITEA_CLIENT_SECRET=【不需要支持则删掉】
- GITEA_URL=【不需要支持则删掉】
- GITLAB_CLIENT_ID=【不需要支持则删掉】
- GITLAB_CLIENT_SECRET=【不需要支持则删掉】
- GITLAB_URL=【不需要支持则删掉】
ports:
- 8080:8080/tcp
network_mode: bridge
restart: always
```
docker-compose方式的启动或停止命令
```bash
# 在 docker-compose.yml 文件目录下 启动命令
docker-compose up -d
# 在 docker-compose.yml 文件目录下 停止命令
docker-compose down
# 更新镜像只需要修改docker-compose.yml中镜像版本执行再停止、启动命令即可
```
或者可以直接通过Docker命名直接启动命令如下
```bash
docker run -itd --name stackedit \
-p 8080:8080 \
-e LISTENING_PORT=8080 \
-e ROOT_URL=/ \
-e USER_BUCKET_NAME=root \
-e DROPBOX_APP_KEY=【不需要支持则删掉】 \
-e DROPBOX_APP_KEY_FULL=【不需要支持则删掉】 \
-e GITHUB_CLIENT_ID=【不需要支持则删掉】 \
-e GITHUB_CLIENT_SECRET=【不需要支持则删掉】 \
-e GITEE_CLIENT_ID=【不需要支持则删掉】 \
-e GITEE_CLIENT_SECRET=【不需要支持则删掉】 \
-e GOOGLE_CLIENT_ID=【不需要支持则删掉】 \
-e GOOGLE_API_KEY=【不需要支持则删掉】 \
-e GITEA_CLIENT_ID=【不需要支持则删掉】 \
-e GITEA_CLIENT_SECRET=【不需要支持则删掉】 \
-e GITEA_URL=【不需要支持则删掉】 \
-e GITLAB_CLIENT_ID=【不需要支持则删掉】 \
-e GITLAB_CLIENT_SECRET=【不需要支持则删掉】 \
-e GITLAB_URL=【不需要支持则删掉】 \
mafgwo/stackedit:【docker中央仓库找到最新版本】
```
## 如何创建三方平台应用
> 部署时如果需要支持Gitee或GitHub则需要自行到对应三方平台创建应用获取到应用ID和秘钥替换到以上的环境变量中再启动应用。
- Gitee的环境变量GITEE_CLIENT_ID、GITEE_CLIENT_SECRET**[如何创建Gitee应用](./docs/部署之Gitee应用创建.md)**
- GitHub的环境变量GITHUB_CLIENT_ID、GITEE_CLIENT_SECRET**[如何创建GitHub应用](./docs/部署之GitHub应用创建.md)**
- Gitea可选择性配置环境变量未配置则在关联时前端指定有配置则仅允许配置的应用信息GITEA_CLIENT_ID、GITEA_CLIENT_SECRET、GITEA_URL**[如何创建Gitea应用](./docs/部署之Gitea应用创建.md)**
- Gitlab可选择性配置环境变量未配置则在关联时前端指定有配置则仅允许配置的应用信息GITLAB_CLIENT_ID、GITLAB_CLIENT_SECRET、GITLAB_URL **如何创建Gitlab应用(待补充文档)**
特别说明自建的Gitea、Gitlab要能接入stackedit必须支持跨域
## 编译与运行
> 编译运行的nodejs版本选择11.15.0版本
```bash
# 安装依赖
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:8080
@ -189,3 +28,27 @@ npm run build
# build for production and view the bundle analyzer report
npm run build --report
```
### StackEdit can:
- Manage multiple Markdown files online or offline
- Export your files in Markdown, HTML, PDF, Word, EPUB...
- Synchronize your Markdown files in the Cloud
- Edit existing Markdown files from Google Drive, Dropbox and your local hard drive
- Post your Markdown file on Blogger/Blogspot, WordPress, Zendesk
- Publish your Markdown file on GitHub, Gist, Google Drive, Dropbox
- Share a workspace over Google Drive, CouchDB
### Features:
- Real-time HTML preview with Scroll Sync feature to bind editor and preview scrollbars
- Markdown Extra/GitHub Flavored Markdown support and Prism.js syntax highlighting
- LaTeX mathematical expressions using KaTeX
- Diagrams and flowcharts using Mermaid
- WYSIWYG control buttons
- Smart layout
- Offline editing
- Online synchronization using Google Drive, Dropbox and GitHub
- One click publish to Blogger, Dropbox, Gist, GitHub, Google Drive, WordPress, Zendesk
> **NOTE:** This page has been written and published with [StackEdit](https://stackedit.io/ "StackEdit").

View File

@ -1,37 +0,0 @@
#!/bin/bash
# 检查参数是否提供版本号
if [ -z "$1" ]; then
echo "请提供版本号作为参数"
exit 1
fi
# 定义版本号变量
VERSION="$1"
IMAGE_NAME="mafgwo/stackedit"
# 构建 Docker 镜像
build_image() {
docker build -t "$IMAGE_NAME" .
}
# 标记 Docker 镜像
tag_image() {
docker tag "$IMAGE_NAME" "$IMAGE_NAME:$VERSION"
docker tag "$IMAGE_NAME" "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME:$VERSION"
}
# 推送 Docker 镜像
push_image() {
docker push "$IMAGE_NAME"
docker push "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME"
docker push "$IMAGE_NAME:$VERSION"
docker push "registry.cn-hangzhou.aliyuncs.com/$IMAGE_NAME:$VERSION"
}
# 执行构建、标记和推送
build_image
tag_image
push_image
echo "操作完成"

View File

@ -1,24 +0,0 @@
#!/bin/bash
set -e
# Tag and push docker image
docker login -u benweet -p "$DOCKER_PASSWORD"
docker tag benweet/stackedit "benweet/stackedit:$TRAVIS_TAG"
docker push benweet/stackedit:$TRAVIS_TAG
docker tag benweet/stackedit:$TRAVIS_TAG benweet/stackedit:latest
docker push benweet/stackedit:latest
# Build the chart
cd "$TRAVIS_BUILD_DIR"
npm run chart
# Add chart to helm repository
git clone --branch master "https://benweet:$GITHUB_TOKEN@github.com/benweet/stackedit-charts.git" /tmp/charts
cd /tmp/charts
helm package "$TRAVIS_BUILD_DIR/dist/stackedit"
helm repo index --url https://benweet.github.io/stackedit-charts/ .
git config user.name "Benoit Schweblin"
git config user.email "benoit.schweblin@gmail.com"
git add .
git commit -m "Added $TRAVIS_TAG"
git push origin master

7
build/docker-push-tag.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash
set -e
docker login -u benweet -p "$DOCKER_PASSWORD"
docker tag benweet/stackedit "benweet/stackedit:$TRAVIS_TAG"
docker push benweet/stackedit:$TRAVIS_TAG
docker tag benweet/stackedit:$TRAVIS_TAG benweet/stackedit:latest
docker push benweet/stackedit:latest

View File

@ -5,6 +5,7 @@ var config = require('../config')
var VueLoaderPlugin = require('vue-loader/lib/plugin')
var vueLoaderConfig = require('./vue-loader.conf')
var StylelintPlugin = require('stylelint-webpack-plugin')
var FaviconsWebpackPlugin = require('favicons-webpack-plugin')
function resolve (dir) {
return path.join(__dirname, '..', dir)
@ -47,33 +48,16 @@ module.exports = {
loader: 'vue-loader',
options: vueLoaderConfig
},
// We can't pass graphlibrary to babel
{
test: /\.js$/,
loader: 'string-replace-loader',
include: [
resolve('node_modules/graphlibrary')
],
options: {
search: '^\\s*(?:let|const) ',
replace: 'var ',
flags: 'gm'
}
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [
resolve('src'),
resolve('test'),
resolve('node_modules/mermaid')
],
include: [resolve('src'), resolve('test'), resolve('node_modules/mermaid/src')],
exclude: [
resolve('node_modules/mermaid/src/diagrams/class/parser'),
resolve('node_modules/mermaid/src/diagrams/flowchart/parser'),
resolve('node_modules/mermaid/src/diagrams/gantt/parser'),
resolve('node_modules/mermaid/src/diagrams/git/parser'),
resolve('node_modules/mermaid/src/diagrams/sequence/parser')
resolve('node_modules/mermaid/src/diagrams/sequence/parser'),
],
},
{
@ -102,6 +86,10 @@ module.exports = {
new StylelintPlugin({
files: ['**/*.vue', '**/*.scss']
}),
new FaviconsWebpackPlugin({
logo: resolve('src/assets/favicon.png'),
title: 'StackEdit',
}),
new webpack.DefinePlugin({
VERSION: JSON.stringify(require('../package.json').version)
})

View File

@ -19,7 +19,9 @@ module.exports = merge(baseWebpackConfig, {
devtool: 'source-map',
plugins: [
new webpack.DefinePlugin({
NODE_ENV: config.dev.env.NODE_ENV
NODE_ENV: config.dev.env.NODE_ENV,
GOOGLE_CLIENT_ID: config.dev.env.GOOGLE_CLIENT_ID,
GITHUB_CLIENT_ID: config.dev.env.GITHUB_CLIENT_ID
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),

View File

@ -9,12 +9,6 @@ var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var OfflinePlugin = require('offline-plugin');
var WebpackPwaManifest = require('webpack-pwa-manifest')
var FaviconsWebpackPlugin = require('favicons-webpack-plugin')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
var env = config.build.env
@ -100,23 +94,6 @@ var webpackConfig = merge(baseWebpackConfig, {
ignore: ['.*']
}
]),
new FaviconsWebpackPlugin({
logo: resolve('src/assets/favicon.png'),
title: 'StackEdit',
}),
new WebpackPwaManifest({
name: 'StackEdit',
description: 'Full-featured, open-source Markdown editor',
display: 'standalone',
orientation: 'any',
start_url: 'app',
background_color: '#ffffff',
crossorigin: 'use-credentials',
icons: [{
src: resolve('src/assets/favicon.png'),
sizes: [96, 128, 192, 256, 384, 512]
}]
}),
new OfflinePlugin({
ServiceWorker: {
events: true
@ -124,7 +101,7 @@ var webpackConfig = merge(baseWebpackConfig, {
AppCache: true,
excludes: ['**/.*', '**/*.map', '**/index.html', '**/static/oauth2/callback.html', '**/icons-*/*.png', '**/static/fonts/KaTeX_*'],
externals: ['/', '/app', '/oauth2/callback']
}),
})
]
})

View File

@ -1,22 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@ -1,5 +0,0 @@
apiVersion: v1
appVersion: vSTACKEDIT_VERSION
description: In-browser Markdown editor
name: stackedit
version: STACKEDIT_VERSION

View File

@ -1,21 +0,0 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "stackedit.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "stackedit.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "stackedit.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "stackedit.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80
{{- end }}

View File

@ -1,45 +0,0 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "stackedit.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "stackedit.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "stackedit.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "stackedit.labels" -}}
app.kubernetes.io/name: {{ include "stackedit.name" . }}
helm.sh/chart: {{ include "stackedit.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}

View File

@ -1,87 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "stackedit.fullname" . }}
labels:
{{ include "stackedit.labels" . | indent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "stackedit.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "stackedit.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
volumeMounts:
- mountPath: /run
name: run-volume
- mountPath: /tmp
name: tmp-volume
env:
- name: PORT
value: "80"
- name: PAYPAL_RECEIVER_EMAIL
value: {{ .Values.paypalReceiverEmail }}
- name: AWS_ACCESS_KEY_ID
value: {{ .Values.awsAccessKeyId }}
- name: AWS_SECRET_ACCESS_KEY
value: {{ .Values.awsSecretAccessKey }}
- name: DROPBOX_APP_KEY
value: {{ .Values.dropboxAppKey }}
- name: DROPBOX_APP_KEY_FULL
value: {{ .Values.dropboxAppKeyFull }}
- name: GOOGLE_CLIENT_ID
value: {{ .Values.googleClientId }}
- name: GOOGLE_API_KEY
value: {{ .Values.googleApiKey }}
- name: GITHUB_CLIENT_ID
value: {{ .Values.githubClientId }}
- name: GITHUB_CLIENT_SECRET
value: {{ .Values.githubClientSecret }}
- name: WORDPRESS_CLIENT_ID
value: {{ .Values.wordpressClientId }}
- name: WORDPRESS_SECRET
value: {{ .Values.wordpressSecret }}
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: run-volume
emptyDir: {}
- name: tmp-volume
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -1,39 +0,0 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "stackedit.fullname" . -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{ include "stackedit.labels" . | indent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ . }}
pathType: Prefix
backend:
service:
name: {{ $fullName }}
port:
name: http
{{- end }}
{{- end }}
{{- end }}

View File

@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "stackedit.fullname" . }}
labels:
{{ include "stackedit.labels" . | indent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "stackedit.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}

View File

@ -1,15 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "stackedit.fullname" . }}-test-connection"
labels:
{{ include "stackedit.labels" . | indent 4 }}
annotations:
"helm.sh/hook": test-success
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "stackedit.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@ -1,71 +0,0 @@
# Default values for stackedit.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
dropboxAppKey: ""
dropboxAppKeyFull: ""
googleClientId: ""
googleApiKey: ""
githubClientId: ""
githubClientSecret: ""
giteeClientId: ""
giteeClientSecret: ""
wordpressClientId: ""
wordpressSecret: ""
paypalReceiverEmail: ""
awsAccessKeyId: ""
awsSecretAccessKey: ""
giteaClientId: ""
giteaClientSecret: ""
giteaUrl: ""
gitlabClientId: ""
gitlabUrl: ""
replicaCount: 1
image:
repository: benweet/stackedit
tag: vSTACKEDIT_VERSION
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations:
# kubernetes.io/ingress.class: nginx
# certmanager.k8s.io/issuer: letsencrypt-prod
# certmanager.k8s.io/acme-challenge-type: http01
hosts: []
# - host: stackedit.example.com
# paths:
# - /
tls: []
# - secretName: stackedit-tls
# hosts:
# - stackedit.example.com
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,28 +1,28 @@
{
"name": "StackEdit中文版",
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
"version": "5.15.17",
"manifest_version": 2,
"container": "GITEE",
"api_console_project_id": "241271498917",
"icons": {
"16": "icon-16.png",
"32": "icon-32.png",
"64": "icon-64.png",
"128": "icon-128.png",
"256": "icon-256.png",
"512": "icon-512.png"
},
"app": {
"urls": [
"https://md.jonylee.top/"
],
"launch": {
"web_url": "https://md.jonylee.top/app"
}
},
"offline_enabled": true,
"permissions": [
"unlimitedStorage"
]
}
"name": "StackEdit",
"description": "In-browser Markdown editor",
"version": "1.0.13",
"manifest_version": 2,
"container" : "GOOGLE_DRIVE",
"api_console_project_id" : "241271498917",
"icons": {
"16": "icon-16.png",
"32": "icon-32.png",
"64": "icon-64.png",
"128": "icon-128.png",
"256": "icon-256.png",
"512": "icon-512.png"
},
"app": {
"urls": [
"https://stackedit.io/"
],
"launch": {
"web_url": "https://stackedit.io/app"
}
},
"offline_enabled": true,
"permissions": [
"unlimitedStorage"
]
}

View File

@ -3,16 +3,6 @@ var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
// 以下配置是开发临时用的配置 随时可能失效 请替换为自己的
GITHUB_CLIENT_ID: '"845b8f75df48f2ee0563"',
GITHUB_CLIENT_SECRET: '"80df676597abded1450926861965cc3f9bead6a0"',
GITEE_CLIENT_ID: '"925ba7c78b85dec984f7877e4aca5cab10ae333c6d68e761bdb0b9dfb8f55672"',
GITEE_CLIENT_SECRET: '"f05731066e42d307339dc8ebbb037a103881dafc7207a359a393b87749f1c562"',
CLIENT_ID: '"thF3qCGLN39OtafjGnqHyj6n02WwE6xD"',
// GITEA_CLIENT_ID: '"fe30f8f9-b1e8-4531-8f72-c1a5d3912805"',
// GITEA_CLIENT_SECRET: '"lus7oMnb3H6M1hsChndphArE20Txr7erwJLf7SDBQWTw"',
// GITEA_URL: '"https://gitea.test.com"',
GITLAB_CLIENT_ID: '"074cd5103c62dea0f479dac861039656ac80935e304c8113a02cc64c629496ae"',
GITLAB_CLIENT_SECRET: '"6f406f24216b686d55d28313dec1913c2a8e599afdb08380d5e8ce838e16e41e"',
GITLAB_URL: '"http://gitlab.qicoder.com"',
})
GOOGLE_CLIENT_ID: '"241271498917-c3loeet001r90q6u79q484bsh5clg4fr.apps.googleusercontent.com"',
GITHUB_CLIENT_ID: '"cbf0cf25cfd026be23e1"'
})

View File

@ -23,8 +23,8 @@ module.exports = {
},
dev: {
env: require('./dev.env'),
port: 80,
autoOpenBrowser: false,
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},

View File

@ -1,3 +1,5 @@
module.exports = {
NODE_ENV: '"production"'
NODE_ENV: '"production"',
GOOGLE_CLIENT_ID: '"241271498917-t4t7d07qis7oc0ahaskbif3ft6tk63cd.apps.googleusercontent.com"',
GITHUB_CLIENT_ID: '"30c1491057c9ad4dbd56"'
}

View File

@ -1,10 +0,0 @@
# 大文档导出PDF方式说明
> 由于大文档导出PDF需要消费非常多的服务器资源而且很容易导致导出超时故导出PDF的MD文档过大时可以使用 **[wkhtmltopdf](https://wkhtmltopdf.org/downloads.html)** 工具导出。
# 操作步骤
- 先在 **[StackEdit中文版](https://stackedit.cn/app)** 中使用 `导出为HTML` 功能导出MD文档导出后可以得到一个HTML文档。
- 到 **[wkhtmltopdf](https://wkhtmltopdf.org/downloads.html)** 官网下载安装程序。
- 使用 wkhtmltopdf 的导出PDF的命令 `wkhtmltopdf [GLOBAL OPTION]... [OBJECT]... <output file>` 把HTML导出为PDF如简单的导出命令`wkhtmltopdf test.html test.pdf`,具体的 `GLOBAL OPTION` 参数说明可以通过 `wkhtmltopdf -H` 查看帮助文档。

View File

@ -1,20 +0,0 @@
# GitHub应用配置说明
> StackEdit中文版部署如果需要支持GitHub则需要到GitHub创建一个应用并复制其中的clientId和clientSecret填充到环境变量 GITHUB_CLIENT_ID 和 GITHUB_CLIENT_SECRET 中。
# 如何创建GitHub应用
按下面图的指示创建
![](../images/github/github01.png)
![](../images/github/github02.png)
![](../images/github/github03.png)
![](../images/github/github04.png)
![](../images/github/github05.png)

View File

@ -1,35 +0,0 @@
# Gitea应用配置说明
> StackEdit中文版支持Gitea则需要到Gitea创建一个应用在StackEdit中文版绑定Gitea账号的时候填入。
# 如何创建Gitea应用
按下面图的指示创建
![](../images/gitea/gitea01.png)
![](../images/gitea/gitea02.png)
![](../images/gitea/gitea03.png)
![](../images/gitea/gitea04.png)
创建成功后即可看到应用ID 和 应用秘钥。
# Gitea跨域问题
由于StackEdit中文版是从浏览器直接访问Gitea接口故个人部署的Gitea需要支持跨域至于如何支持跨域请参考官方文档https://docs.gitea.io/en-us/config-cheat-sheet/#cors-cors (官方跨域的支持好像存在问题我个人包括很多网友通过这个配置支持跨域都失败了如果你也失败了可以试试用nginx代理实现跨域)
nginx配置实现跨域的配置如下
```
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
if ($request_method = 'OPTIONS') {
return 204;
}
```

View File

@ -1,19 +0,0 @@
# Gitee应用配置说明
> StackEdit中文版部署如果需要支持Gitee则需要到Gitee创建一个应用并复制其中的clientId和clientSecret填充到环境变量 GITEE_CLIENT_ID 和 GITEE_CLIENT_SECRET 中。
# 如何创建Gitee应用
按下面图的指示创建
![](../images/gitee/gitee01.png)
![](../images/gitee/gitee02.png)
![](../images/gitee/gitee03.png)
![](../images/gitee/gitee04.png)
创建成功后即可看到client id 和 client secret。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 761 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

View File

@ -1,31 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8">
<title>Markdown编辑器-JonyLee的设计导航</title>
<link rel="canonical" href="https://md.jonylee.top">
<meta name="description" content="StackEdit中文版免费开源功能全面的Markdown编辑器。">
<title>StackEdit</title>
<link rel="canonical" href="https://stackedit.io/app">
<meta name="description" content="Free, open-source, full-featured Markdown editor.">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
</head>
<body>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<!-- baidu统计 -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?dad4b4383b13eedea1ab45ee323df1c3";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<!-- baidu统计结束 -->
</body>
</html>
</body>
</html>

View File

@ -11,7 +11,7 @@ const express = require('express');
const app = express();
require('./server')(app);
require('./server')(app, process.env.SERVE_V4);
const port = parseInt(process.env.PORT || 8080, 10);
const httpServer = http.createServer(app);

4939
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
{
"name": "stackedit",
"version": "5.15.21",
"description": "免费, 开源, 功能齐全的 Markdown 编辑器",
"author": "Benoit Schweblin, 豆萁",
"version": "5.12.2",
"description": "Free, open-source, full-featured Markdown editor",
"author": "Benoit Schweblin",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/mafgwo/stackedit/issues"
"url": "https://github.com/benweet/stackedit/issues"
},
"main": "index.js",
"scripts": {
@ -21,12 +21,12 @@
"postversion": "git push origin master --tags && npm publish",
"patch": "npm version patch -m \"Tag v%s\"",
"minor": "npm version minor -m \"Tag v%s\"",
"major": "npm version major -m \"Tag v%s\"",
"chart": "mkdir -p dist && rm -rf dist/stackedit && cp -r chart dist/stackedit && sed -i.bak -e s/STACKEDIT_VERSION/$npm_package_version/g dist/stackedit/*.yaml && rm dist/stackedit/*.yaml.bak"
"major": "npm version major -m \"Tag v%s\""
},
"dependencies": {
"@vue/test-utils": "^1.0.0-beta.16",
"abcjs": "^5.2.0",
"aws-sdk": "^2.317.0",
"babel-runtime": "^6.26.0",
"bezier-easing": "^1.1.0",
"body-parser": "^1.18.2",
@ -34,10 +34,11 @@
"compression": "^1.7.0",
"diff-match-patch": "^1.0.0",
"file-saver": "^1.3.8",
"google-id-token-verifier": "^0.2.3",
"handlebars": "^4.0.10",
"indexeddbshim": "^3.6.2",
"js-yaml": "^3.11.0",
"katex": "^0.16.2",
"katex": "^v0.10.0-rc.1",
"markdown-it": "^8.4.1",
"markdown-it-abbr": "^1.0.4",
"markdown-it-deflist": "^2.0.2",
@ -48,14 +49,14 @@
"markdown-it-pandoc-renderer": "1.1.3",
"markdown-it-sub": "^1.0.0",
"markdown-it-sup": "^1.0.0",
"mermaid": "^8.9.2",
"mermaid": "^8.0.0-rc.8",
"mousetrap": "^1.6.1",
"normalize-scss": "^7.0.1",
"prismjs": "^1.6.0",
"request": "^2.85.0",
"serve-static": "^1.13.2",
"tmp": "^0.0.33",
"turndown": "^7.1.1",
"turndown": "^4.0.2",
"vue": "^2.5.16",
"vuex": "^3.0.1"
},
@ -89,7 +90,7 @@
"favicons-webpack-plugin": "^0.0.9",
"file-loader": "^1.1.11",
"friendly-errors-webpack-plugin": "^1.7.0",
"gulp": "^4.0.2",
"gulp": "^3.9.1",
"gulp-concat": "^2.6.1",
"html-webpack-plugin": "^3.2.0",
"http-proxy-middleware": "^0.18.0",
@ -98,20 +99,17 @@
"jest": "^23.0.0",
"jest-raw-loader": "^1.0.1",
"jest-serializer-vue": "^0.3.0",
"js-md5": "^0.7.3",
"node-sass": "^4.0.0",
"node-sass": "^4.9.0",
"npm-bump": "^0.0.23",
"offline-plugin": "^5.0.3",
"opn": "^4.0.2",
"optimize-css-assets-webpack-plugin": "^1.3.2",
"ora": "^1.2.0",
"raw-loader": "^0.5.1",
"replace-in-file": "^4.1.0",
"rimraf": "^2.6.0",
"sass-loader": "^7.0.1",
"semver": "^5.5.0",
"shelljs": "^0.8.1",
"string-replace-loader": "^2.1.1",
"stylelint": "^9.2.0",
"stylelint-config-standard": "^16.0.0",
"stylelint-processor-html": "^1.0.0",
@ -122,11 +120,10 @@
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"webpack": "^2.6.1",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.2",
"webpack-pwa-manifest": "^3.7.1",
"worker-loader": "^1.1.1"
},
"engines": {

View File

@ -1,54 +0,0 @@
const pandocPath = process.env.PANDOC_PATH || 'pandoc';
const wkhtmltopdfPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf';
const paypalReceiverEmail = process.env.PAYPAL_RECEIVER_EMAIL;
const dropboxAppKey = process.env.DROPBOX_APP_KEY;
const dropboxAppKeyFull = process.env.DROPBOX_APP_KEY_FULL;
const githubClientId = process.env.GITHUB_CLIENT_ID;
const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
const giteeClientId = process.env.GITEE_CLIENT_ID;
const giteeClientSecret = process.env.GITEE_CLIENT_SECRET;
const googleClientId = process.env.GOOGLE_CLIENT_ID;
const googleApiKey = process.env.GOOGLE_API_KEY;
const wordpressClientId = process.env.WORDPRESS_CLIENT_ID;
const giteaClientId = process.env.GITEA_CLIENT_ID;
const giteaClientSecret = process.env.GITEA_CLIENT_SECRET;
const giteaUrl = process.env.GITEA_URL;
const gitlabClientId = process.env.GITLAB_CLIENT_ID;
const gitlabClientSecret = process.env.GITLAB_CLIENT_SECRET;
const gitlabUrl = process.env.GITLAB_URL;
exports.values = {
pandocPath,
wkhtmltopdfPath,
paypalReceiverEmail,
dropboxAppKey,
dropboxAppKeyFull,
githubClientId,
githubClientSecret,
giteeClientId,
giteeClientSecret,
googleClientId,
googleApiKey,
wordpressClientId,
giteaClientId,
giteaClientSecret,
giteaUrl,
gitlabClientId,
gitlabClientSecret,
gitlabUrl,
};
exports.publicValues = {
dropboxAppKey,
dropboxAppKeyFull,
githubClientId,
googleClientId,
googleApiKey,
wordpressClientId,
allowSponsorship: !!paypalReceiverEmail,
giteaClientId,
giteaUrl,
gitlabClientId,
gitlabUrl,
};

View File

@ -1,40 +0,0 @@
const request = require('request');
const conf = require('./conf');
function giteaToken(queryParam) {
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: `${conf.values.giteaUrl}/login/oauth/access_token`,
headers: {
'content-type': 'application/json',
},
json: true,
body: {
...queryParam,
client_id: conf.values.giteaClientId,
client_secret: conf.values.giteaClientSecret,
},
}, (err, res, body) => {
if (err) {
reject(err);
}
const token = body.access_token;
if (token) {
resolve(body);
} else {
reject(res.statusCode + ',body:' + JSON.stringify(body));
}
});
});
}
exports.giteaToken = (req, res) => {
giteaToken(req.query)
.then(
tokenBody => res.send(tokenBody),
err => res
.status(400)
.send(err ? err.message || err.toString() : 'bad_code'),
);
};

View File

@ -1,42 +0,0 @@
const qs = require('qs'); // eslint-disable-line import/no-extraneous-dependencies
const request = require('request');
const conf = require('./conf');
function giteeToken(clientId, code, oauth2RedirectUri) {
const clientIndex = conf.values.giteeClientId.split(',').indexOf(clientId);
const clientSecret = conf.values.giteeClientSecret.split(',')[clientIndex];
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: 'https://gitee.com/oauth/token',
form: {
client_id: clientId,
client_secret: clientSecret,
code,
grant_type: 'authorization_code',
redirect_uri: oauth2RedirectUri,
},
json: true
}, (err, res, body) => {
if (err) {
reject(err);
}
const token = body.access_token;
if (token) {
resolve(body);
} else {
reject(res.statusCode + ',body:' + JSON.stringify(body));
}
});
});
}
exports.giteeToken = (req, res) => {
giteeToken(req.query.clientId, req.query.code, req.query.oauth2RedirectUri)
.then(
tokenBody => res.send(tokenBody),
err => res
.status(400)
.send(err ? err.message || err.toString() : 'bad_code'),
);
};

View File

@ -1,6 +1,5 @@
const qs = require('qs'); // eslint-disable-line import/no-extraneous-dependencies
const request = require('request');
const conf = require('./conf');
function githubToken(clientId, code) {
return new Promise((resolve, reject) => {
@ -9,7 +8,7 @@ function githubToken(clientId, code) {
url: 'https://github.com/login/oauth/access_token',
qs: {
client_id: clientId,
client_secret: conf.values.githubClientSecret,
client_secret: process.env.GITHUB_SECRET,
code,
},
}, (err, res, body) => {

View File

@ -1,40 +0,0 @@
const request = require('request');
const conf = require('./conf');
function gitlabToken(queryParam) {
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: `${conf.values.gitlabUrl}/oauth/token`,
headers: {
'content-type': 'application/json',
},
json: true,
qs: {
...queryParam,
client_id: conf.values.gitlabClientId,
client_secret: conf.values.gitlabClientSecret,
},
}, (err, res, body) => {
if (err) {
reject(err);
}
const token = body.access_token;
if (token) {
resolve(body);
} else {
reject(res.statusCode + ',body:' + JSON.stringify(body));
}
});
});
}
exports.gitlabToken = (req, res) => {
gitlabToken(req.query)
.then(
tokenBody => res.send(tokenBody),
err => res
.status(400)
.send(err ? err.message || err.toString() : 'bad_code'),
);
};

View File

@ -2,17 +2,14 @@ const compression = require('compression');
const serveStatic = require('serve-static');
const bodyParser = require('body-parser');
const path = require('path');
const user = require('./user');
const github = require('./github');
const gitee = require('./gitee');
const gitea = require('./gitea');
const gitlab = require('./gitlab');
const pdf = require('./pdf');
const pandoc = require('./pandoc');
const conf = require('./conf');
const resolvePath = pathToResolve => path.join(__dirname, '..', pathToResolve);
module.exports = (app) => {
module.exports = (app, serveV4) => {
if (process.env.NODE_ENV === 'production') {
// Enable CORS for fonts
app.all('*', (req, res, next) => {
@ -27,33 +24,23 @@ module.exports = (app) => {
}
app.get('/oauth2/githubToken', github.githubToken);
app.get('/oauth2/giteeToken', gitee.giteeToken);
app.get('/oauth2/giteaToken', gitea.giteaToken);
app.get('/oauth2/gitlabToken', gitlab.gitlabToken);
app.get('/conf', (req, res) => res.send(conf.publicValues));
app.get('/userInfo', user.userInfo);
app.post('/pdfExport', pdf.generate);
app.post('/pandocExport', pandoc.generate);
app.get('/giteeClientId', (req, res) => {
const giteeClientIds = conf.values.giteeClientId.split(',');
// 仅一个 则直接返回
if (giteeClientIds.length === 1) {
res.send(giteeClientIds[0]);
return;
}
// 是否随机一个clientId 默认第一个 如果random 为1 则使用随机 避免单个应用接口次数用满无法自动切换其他应用
const random = req.query.random;
if (!random) {
res.send(giteeClientIds[0]);
return;
}
// 随机一个 排除第一个 因为第一个应用接口次数用完了
const clientId = giteeClientIds[Math.floor(((giteeClientIds.length - 1) * Math.random())) + 1];
res.send(clientId);
});
app.post('/paypalIpn', bodyParser.urlencoded({
extended: false,
}), user.paypalIpn);
if (serveV4) {
/* eslint-disable global-require, import/no-unresolved */
app.post('/sshPublish', require('../stackedit_v4/app/ssh').publish);
app.post('/picasaImportImg', require('../stackedit_v4/app/picasa').importImg);
app.get('/downloadImport', require('../stackedit_v4/app/download').importPublic);
/* eslint-enable global-require, import/no-unresolved */
}
// Serve landing.html
app.get('/', (req, res) => res.sendFile(resolvePath('static/landing/index.html')));
// Serve privacy_policy.html
app.get('/privacy_policy.html', (req, res) => res.sendFile(resolvePath('static/landing/privacy_policy.html')));
// Serve sitemap.xml
app.get('/sitemap.xml', (req, res) => res.sendFile(resolvePath('static/sitemap.xml')));
// Serve callback.html
@ -61,29 +48,36 @@ module.exports = (app) => {
// Google Drive action receiver
app.get('/googleDriveAction', (req, res) =>
res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`));
// Serve the static folder with 30 day max-age
app.use('/themes', serveStatic(resolvePath('static/themes'), {
maxAge: '5d',
}));
// Serve style.css with 1 day max-age
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
maxAge: '1d',
}));
// Serve share.html
app.get('/share.html', (req, res) => res.sendFile(resolvePath('static/landing/share.html')));
app.get('/gistshare.html', (req, res) => res.sendFile(resolvePath('static/landing/gistshare.html')));
// Serve static resources
if (process.env.NODE_ENV === 'production') {
if (serveV4) {
// Serve editor.html in /viewer
app.get('/editor', (req, res) => res.sendFile(resolvePath('stackedit_v4/views/editor.html')));
// Serve viewer.html in /viewer
app.get('/viewer', (req, res) => res.sendFile(resolvePath('stackedit_v4/views/viewer.html')));
}
// Serve index.html in /app
app.get('/app', (req, res) => res.sendFile(resolvePath('dist/index.html')));
// Serve style.css with 1 day max-age
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
maxAge: '1d',
}));
// Serve the static folder with 1 year max-age
app.use('/static', serveStatic(resolvePath('dist/static'), {
maxAge: '1y',
}));
app.use(serveStatic(resolvePath('dist')));
if (serveV4) {
app.use(serveStatic(path.dirname(resolvePath('stackedit_v4/public/cache.manifest'))));
// Error 404
app.use((req, res) => res.status(404).sendFile(resolvePath('stackedit_v4/views/error_404.html')));
}
}
};

View File

@ -2,7 +2,7 @@
const { spawn } = require('child_process');
const fs = require('fs');
const tmp = require('tmp');
const conf = require('./conf');
const user = require('./user');
const outputFormats = {
asciidoc: 'text/plain',
@ -41,101 +41,112 @@ exports.generate = (req, res) => {
const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
? req.query.format
: 'pdf';
new Promise((resolve, reject) => {
tmp.file({
postfix: `.${outputFormat}`,
}, (err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
} else {
resolve({
filePath,
cleanupCallback,
});
Promise.all([
user.checkSponsor(req.query.idToken),
user.checkMonetize(req.query.token),
])
.then(([isSponsor, isMonetize]) => {
if (!isSponsor && !isMonetize) {
throw new Error('unauthorized');
}
});
}).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
const options = readJson(req.query.options);
const metadata = readJson(req.query.metadata);
const params = [];
params.push('--pdf-engine=xelatex');
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
if (options.toc) {
params.push('--toc');
}
options.tocDepth = parseInt(options.tocDepth, 10);
if (!Number.isNaN(options.tocDepth)) {
params.push('--toc-depth', options.tocDepth);
}
options.highlightStyle = highlightStyles.includes(options.highlightStyle) ? options.highlightStyle : 'kate';
params.push('--highlight-style', options.highlightStyle);
Object.keys(metadata).forEach((key) => {
params.push('-M', `${key}=${metadata[key]}`);
});
return new Promise((resolve, reject) => {
tmp.file({
postfix: `.${outputFormat}`,
}, (err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
} else {
resolve({
filePath,
cleanupCallback,
});
}
});
});
})
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
const options = readJson(req.query.options);
const metadata = readJson(req.query.metadata);
const params = [];
let finished = false;
params.push('--latex-engine=xelatex');
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
if (options.toc) {
params.push('--toc');
}
options.tocDepth = parseInt(options.tocDepth, 10);
if (!Number.isNaN(options.tocDepth)) {
params.push('--toc-depth', options.tocDepth);
}
options.highlightStyle = highlightStyles.indexOf(options.highlightStyle) !== -1 ? options.highlightStyle : 'kate';
params.push('--highlight-style', options.highlightStyle);
Object.keys(metadata).forEach((key) => {
params.push('-M', `${key}=${metadata[key]}`);
});
function onError(error) {
finished = true;
cleanupCallback();
reject(error);
}
let finished = false;
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
params.push('-f', 'json', '-t', format, '-o', filePath);
const pandoc = spawn(conf.values.pandocPath, params, {
stdio: [
'pipe',
'ignore',
'pipe',
],
});
let timeoutId = setTimeout(() => {
timeoutId = null;
pandoc.kill();
}, 50000);
pandoc.on('error', onError);
pandoc.stdin.on('error', onError);
pandoc.stderr.on('data', (data) => {
pandocError += `${data}`;
});
pandoc.on('close', (code) => {
if (!finished) {
clearTimeout(timeoutId);
if (!timeoutId) {
res.statusCode = 408;
cleanupCallback();
reject(new Error('timeout'));
} else if (code) {
cleanupCallback();
reject();
} else {
res.set('Content-Type', outputFormats[outputFormat]);
const readStream = fs.createReadStream(filePath);
readStream.on('open', () => readStream.pipe(res));
readStream.on('close', () => cleanupCallback());
readStream.on('error', () => {
function onError(error) {
finished = true;
cleanupCallback();
reject(error);
}
const binPath = process.env.PANDOC_PATH || 'pandoc';
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
params.push('-f', 'json', '-t', format, '-o', filePath);
const pandoc = spawn(binPath, params, {
stdio: [
'pipe',
'ignore',
'pipe',
],
});
let timeoutId = setTimeout(() => {
timeoutId = null;
pandoc.kill();
}, 50000);
pandoc.on('error', onError);
pandoc.stdin.on('error', onError);
pandoc.stderr.on('data', (data) => {
pandocError += `${data}`;
});
pandoc.on('close', (code) => {
if (!finished) {
clearTimeout(timeoutId);
if (!timeoutId) {
res.statusCode = 408;
cleanupCallback();
reject(new Error('timeout'));
} else if (code) {
cleanupCallback();
reject();
});
} else {
res.set('Content-Type', outputFormats[outputFormat]);
const readStream = fs.createReadStream(filePath);
readStream.on('open', () => readStream.pipe(res));
readStream.on('close', () => cleanupCallback());
readStream.on('error', () => {
cleanupCallback();
reject();
});
}
}
});
req.pipe(pandoc.stdin);
}))
.catch((err) => {
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;
res.end('Unauthorized.');
} else if (message === 'timeout') {
res.statusCode = 408;
res.end('Request timeout.');
} else {
res.statusCode = 400;
res.end(pandocError || 'Unknown error.');
}
});
req.pipe(pandoc.stdin);
}))
.catch((err) => {
console.error(err);
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;
res.end('Unauthorized.');
} else if (message === 'timeout') {
res.statusCode = 408;
res.end('Request timeout.');
} else {
res.statusCode = 400;
res.end(pandocError || 'Unknown error.');
}
});
};

View File

@ -2,7 +2,7 @@
const { spawn } = require('child_process');
const fs = require('fs');
const tmp = require('tmp');
const conf = require('./conf');
const user = require('./user');
/* eslint-disable no-var, prefer-arrow-callback, func-names */
function waitForJavaScript() {
@ -50,129 +50,139 @@ const readJson = (str) => {
exports.generate = (req, res) => {
let wkhtmltopdfError = '';
new Promise((resolve, reject) => {
tmp.file((err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
} else {
resolve({
filePath,
cleanupCallback,
});
Promise.all([
user.checkSponsor(req.query.idToken),
user.checkMonetize(req.query.token),
])
.then(([isSponsor, isMonetize]) => {
if (!isSponsor && !isMonetize) {
throw new Error('unauthorized');
}
});
}).then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
let finished = false;
return new Promise((resolve, reject) => {
tmp.file((err, filePath, fd, cleanupCallback) => {
if (err) {
reject(err);
} else {
resolve({
filePath,
cleanupCallback,
});
}
});
});
})
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
let finished = false;
function onError(err) {
finished = true;
cleanupCallback();
reject(err);
}
const options = readJson(req.query.options);
const params = [];
function onError(err) {
finished = true;
cleanupCallback();
reject(err);
}
const options = readJson(req.query.options);
const params = [];
// Margins
const marginTop = parseInt(`${options.marginTop}`, 10);
params.push('-T', Number.isNaN(marginTop) ? 25 : marginTop);
const marginRight = parseInt(`${options.marginRight}`, 10);
params.push('-R', Number.isNaN(marginRight) ? 25 : marginRight);
const marginBottom = parseInt(`${options.marginBottom}`, 10);
params.push('-B', Number.isNaN(marginBottom) ? 25 : marginBottom);
const marginLeft = parseInt(`${options.marginLeft}`, 10);
params.push('-L', Number.isNaN(marginLeft) ? 25 : marginLeft);
// Margins
const marginTop = parseInt(`${options.marginTop}`, 10);
params.push('-T', Number.isNaN(marginTop) ? 25 : marginTop);
const marginRight = parseInt(`${options.marginRight}`, 10);
params.push('-R', Number.isNaN(marginRight) ? 25 : marginRight);
const marginBottom = parseInt(`${options.marginBottom}`, 10);
params.push('-B', Number.isNaN(marginBottom) ? 25 : marginBottom);
const marginLeft = parseInt(`${options.marginLeft}`, 10);
params.push('-L', Number.isNaN(marginLeft) ? 25 : marginLeft);
// Header
if (options.headerCenter) {
params.push('--header-center', `${options.headerCenter}`);
}
if (options.headerLeft) {
params.push('--header-left', `${options.headerLeft}`);
}
if (options.headerRight) {
params.push('--header-right', `${options.headerRight}`);
}
if (options.headerFontName) {
params.push('--header-font-name', `${options.headerFontName}`);
}
if (options.headerFontSize) {
params.push('--header-font-size', `${options.headerFontSize}`);
}
// Header
if (options.headerCenter) {
params.push('--header-center', `${options.headerCenter}`);
}
if (options.headerLeft) {
params.push('--header-left', `${options.headerLeft}`);
}
if (options.headerRight) {
params.push('--header-right', `${options.headerRight}`);
}
if (options.headerFontName) {
params.push('--header-font-name', `${options.headerFontName}`);
}
if (options.headerFontSize) {
params.push('--header-font-size', `${options.headerFontSize}`);
}
// Footer
if (options.footerCenter) {
params.push('--footer-center', `${options.footerCenter}`);
}
if (options.footerLeft) {
params.push('--footer-left', `${options.footerLeft}`);
}
if (options.footerRight) {
params.push('--footer-right', `${options.footerRight}`);
}
if (options.footerFontName) {
params.push('--footer-font-name', `${options.footerFontName}`);
}
if (options.footerFontSize) {
params.push('--footer-font-size', `${options.footerFontSize}`);
}
// Footer
if (options.footerCenter) {
params.push('--footer-center', `${options.footerCenter}`);
}
if (options.footerLeft) {
params.push('--footer-left', `${options.footerLeft}`);
}
if (options.footerRight) {
params.push('--footer-right', `${options.footerRight}`);
}
if (options.footerFontName) {
params.push('--footer-font-name', `${options.footerFontName}`);
}
if (options.footerFontSize) {
params.push('--footer-font-size', `${options.footerFontSize}`);
}
// Page size
params.push('--page-size', !authorizedPageSizes.includes(options.pageSize) ? 'A4' : options.pageSize);
// Page size
params.push('--page-size', authorizedPageSizes.indexOf(options.pageSize) === -1 ? 'A4' : options.pageSize);
// Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason
params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`);
params.push('--window-status', 'done');
const wkhtmltopdf = spawn(conf.values.wkhtmltopdfPath, params.concat('-', filePath), {
stdio: [
'pipe',
'ignore',
'pipe',
],
});
let timeoutId = setTimeout(function () {
timeoutId = null;
wkhtmltopdf.kill();
}, 50000);
wkhtmltopdf.on('error', onError);
wkhtmltopdf.stdin.on('error', onError);
wkhtmltopdf.stderr.on('data', (data) => {
wkhtmltopdfError += `${data}`;
});
wkhtmltopdf.on('close', (code) => {
if (!finished) {
clearTimeout(timeoutId);
if (!timeoutId) {
cleanupCallback();
reject(new Error('timeout'));
} else if (code) {
cleanupCallback();
reject();
} else {
res.set('Content-Type', 'application/pdf');
const readStream = fs.createReadStream(filePath);
readStream.on('open', () => readStream.pipe(res));
readStream.on('close', () => cleanupCallback());
readStream.on('error', () => {
// Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason
const binPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf';
params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`);
params.push('--window-status', 'done');
const wkhtmltopdf = spawn(binPath, params.concat('-', filePath), {
stdio: [
'pipe',
'ignore',
'pipe',
],
});
let timeoutId = setTimeout(function () {
timeoutId = null;
wkhtmltopdf.kill();
}, 50000);
wkhtmltopdf.on('error', onError);
wkhtmltopdf.stdin.on('error', onError);
wkhtmltopdf.stderr.on('data', (data) => {
wkhtmltopdfError += `${data}`;
});
wkhtmltopdf.on('close', (code) => {
if (!finished) {
clearTimeout(timeoutId);
if (!timeoutId) {
cleanupCallback();
reject(new Error('timeout'));
} else if (code) {
cleanupCallback();
reject();
});
} else {
res.set('Content-Type', 'application/pdf');
const readStream = fs.createReadStream(filePath);
readStream.on('open', () => readStream.pipe(res));
readStream.on('close', () => cleanupCallback());
readStream.on('error', () => {
cleanupCallback();
reject();
});
}
}
});
req.pipe(wkhtmltopdf.stdin);
}))
.catch((err) => {
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;
res.end('Unauthorized.');
} else if (message === 'timeout') {
res.statusCode = 408;
res.end('Request timeout.');
} else {
res.statusCode = 400;
res.end(wkhtmltopdfError || 'Unknown error.');
}
});
req.pipe(wkhtmltopdf.stdin);
}))
.catch((err) => {
console.error(err);
const message = err && err.message;
if (message === 'unauthorized') {
res.statusCode = 401;
res.end('Unauthorized.');
} else if (message === 'timeout') {
res.statusCode = 408;
res.end('Request timeout.');
} else {
res.statusCode = 400;
res.end(wkhtmltopdfError || 'Unknown error.');
}
});
};

136
server/user.js Normal file
View File

@ -0,0 +1,136 @@
const request = require('request');
const AWS = require('aws-sdk');
const verifier = require('google-id-token-verifier');
const {
USER_BUCKET_NAME = 'stackedit-users',
PAYPAL_URI = 'https://www.paypal.com/cgi-bin/webscr',
PAYPAL_RECEIVER_EMAIL = 'stackedit.project@gmail.com',
GOOGLE_CLIENT_ID,
} = process.env;
const s3Client = new AWS.S3();
const cb = (resolve, reject) => (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
};
exports.getUser = id => new Promise((resolve, reject) => {
s3Client.getObject({
Bucket: USER_BUCKET_NAME,
Key: id,
}, cb(resolve, reject));
})
.then(
res => JSON.parse(`${res.Body}`),
(err) => {
if (err.code !== 'NoSuchKey') {
throw err;
}
},
);
exports.putUser = (id, user) => new Promise((resolve, reject) => {
s3Client.putObject({
Bucket: USER_BUCKET_NAME,
Key: id,
Body: JSON.stringify(user),
}, cb(resolve, reject));
});
exports.removeUser = id => new Promise((resolve, reject) => {
s3Client.deleteObject({
Bucket: USER_BUCKET_NAME,
Key: id,
}, cb(resolve, reject));
});
exports.getUserFromToken = idToken => new Promise((resolve, reject) => verifier
.verify(idToken, GOOGLE_CLIENT_ID, cb(resolve, reject)))
.then(tokenInfo => exports.getUser(tokenInfo.sub));
exports.userInfo = (req, res) => exports.getUserFromToken(req.query.idToken)
.then(
user => res.send(Object.assign({
sponsorUntil: 0,
}, user)),
err => res
.status(400)
.send(err ? err.message || err.toString() : 'invalid_token'),
);
exports.paypalIpn = (req, res, next) => Promise.resolve()
.then(() => {
const userId = req.body.custom;
const paypalEmail = req.body.payer_email;
const gross = parseFloat(req.body.mc_gross);
let sponsorUntil;
if (gross === 5) {
sponsorUntil = Date.now() + (3 * 31 * 24 * 60 * 60 * 1000); // 3 months
} else if (gross === 15) {
sponsorUntil = Date.now() + (366 * 24 * 60 * 60 * 1000); // 1 year
} else if (gross === 25) {
sponsorUntil = Date.now() + (2 * 366 * 24 * 60 * 60 * 1000); // 2 years
} else if (gross === 50) {
sponsorUntil = Date.now() + (5 * 366 * 24 * 60 * 60 * 1000); // 5 years
}
if (
req.body.receiver_email !== PAYPAL_RECEIVER_EMAIL ||
req.body.payment_status !== 'Completed' ||
req.body.mc_currency !== 'USD' ||
(req.body.txn_type !== 'web_accept' && req.body.txn_type !== 'subscr_payment') ||
!userId || !sponsorUntil
) {
// Ignoring PayPal IPN
return res.end();
}
// Processing PayPal IPN
req.body.cmd = '_notify-validate';
return new Promise((resolve, reject) => request.post({
uri: PAYPAL_URI,
form: req.body,
}, (err, response, body) => {
if (err) {
reject(err);
} else if (body !== 'VERIFIED') {
reject(new Error('PayPal IPN unverified'));
} else {
resolve();
}
}))
.then(() => exports.putUser(userId, {
paypalEmail,
sponsorUntil,
}))
.then(() => res.end());
})
.catch(next);
exports.checkSponsor = (idToken) => {
if (!idToken) {
return Promise.resolve(false);
}
return exports.getUserFromToken(idToken)
.then(userInfo => userInfo && userInfo.sponsorUntil > Date.now(), () => false);
};
exports.checkMonetize = (token) => {
if (!token) {
return Promise.resolve(false);
}
return new Promise(resolve => request({
uri: 'https://monetizejs.com/api/payments',
qs: {
access_token: token,
},
json: true,
}, (err, paymentsRes, payments) => {
const authorized = payments && payments.app === 'ESTHdCYOi18iLhhO' && (
(payments.chargeOption && payments.chargeOption.alias === 'once') ||
(payments.subscriptionOption && payments.subscriptionOption.alias === 'yearly'));
resolve(!err && paymentsRes.statusCode === 200 && authorized);
}));
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path d="M20.512 178.499c-3.359-.884-6.258-2.184-8.931-4.006-2.257-1.538-5.556-4.717-6.811-6.563-1.532-2.255-3.293-6.117-4.011-8.795-.732-2.732-.743-3.82-.757-69.395-.013-65.245.002-66.679.72-69.483C3.259 10.341 11.117 2.797 21.251.546c2.914-.647 133.08-.76 136.223-.118 8.509 1.738 15.198 6.846 19.068 14.564 3.078 6.135 2.803-.617 2.943 72.231.09 46.349.007 65.808-.288 68.232-1.386 11.345-9.211 20.143-20.471 23.019-2.88.735-3.882.746-69.275.726-63.227-.019-66.474-.052-68.939-.701z" fill="#f57d00"/><path d="M115.162 144.835c8.064-1.1 14.384-4.333 20.313-10.39 4.289-4.382 6.974-9.125 8.728-15.419.729-2.615.79-3.888.924-19.242.101-11.588.017-17.015-.285-18.385-.437-1.986-1.677-3.83-3.092-4.599-.435-.237-3.224-.538-6.198-.67-4.982-.221-5.54-.318-7.113-1.24-2.494-1.462-3.181-3.041-3.188-7.327-.013-8.189-3.421-15.792-10.155-22.654-4.797-4.889-10.149-8.198-16.257-10.052-1.462-.444-4.736-.595-15.702-.725-17.207-.203-21.026.15-26.884 2.483-10.8 4.302-18.56 13.368-21.39 24.99-.532 2.183-.635 5.682-.761 25.779-.157 25.177.016 28.874 1.59 33.864 1.299 4.122 2.611 6.648 5.313 10.234 5.146 6.83 12.86 11.763 20.572 13.156 3.67.663 48.948.829 53.585.197z" fill="#fff"/><path d="M67.575 75.717c-4.123-1.136-5.663-7.051-2.633-10.111 1.937-1.955 2.472-2.029 14.595-2.029 10.883 0 11.249.023 12.848.831 2.31 1.167 3.314 2.812 3.314 5.432 0 2.367-.943 4.025-3.046 5.357-1.129.716-1.804.76-12.467.823-6.584.039-11.83-.087-12.611-.303zM67.058 115.526c-1.769-.771-3.417-2.913-3.702-4.813-.272-1.809.638-4.296 2.032-5.558 1.757-1.59 2.528-1.643 24.134-1.66 22.227-.017 22.111-.027 24.219 1.941 2.976 2.78 2.349 7.728-1.239 9.76l-3.686.6-19.213.224c-16.883.198-21.666-.111-22.545-.494z" fill="#f57d00"/></svg>
<svg
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180">
<g>
<path d="M20.512,178.499c-3.359,-0.884 -6.258,-2.184 -8.931,-4.006c-2.257,-1.538 -5.556,-4.717 -6.811,-6.563c-1.532,-2.255 -3.293,-6.117 -4.011,-8.795c-0.732,-2.732 -0.743,-3.82 -0.757,-69.395c-0.013,-65.245 0.002,-66.679 0.72,-69.483c2.537,-9.916 10.395,-17.46 20.529,-19.711c2.914,-0.647 133.08,-0.76 136.223,-0.118c8.509,1.738 15.198,6.846 19.068,14.564c3.078,6.135 2.803,-0.617 2.943,72.231c0.09,46.349 0.007,65.808 -0.288,68.232c-1.386,11.345 -9.211,20.143 -20.471,23.019c-2.88,0.735 -3.882,0.746 -69.275,0.726c-63.227,-0.019 -66.474,-0.052 -68.939,-0.701l0,0Z" style="fill:#f57d00;fill-rule:nonzero;"/>
<path d="M115.162,144.835c8.064,-1.1 14.384,-4.333 20.313,-10.39c4.289,-4.382 6.974,-9.125 8.728,-15.419c0.729,-2.615 0.79,-3.888 0.924,-19.242c0.101,-11.588 0.017,-17.015 -0.285,-18.385c-0.437,-1.986 -1.677,-3.83 -3.092,-4.599c-0.435,-0.237 -3.224,-0.538 -6.198,-0.67c-4.982,-0.221 -5.54,-0.318 -7.113,-1.24c-2.494,-1.462 -3.181,-3.041 -3.188,-7.327c-0.013,-8.189 -3.421,-15.792 -10.155,-22.654c-4.797,-4.889 -10.149,-8.198 -16.257,-10.052c-1.462,-0.444 -4.736,-0.595 -15.702,-0.725c-17.207,-0.203 -21.026,0.15 -26.884,2.483c-10.8,4.302 -18.56,13.368 -21.39,24.99c-0.532,2.183 -0.635,5.682 -0.761,25.779c-0.157,25.177 0.016,28.874 1.59,33.864c1.299,4.122 2.611,6.648 5.313,10.234c5.146,6.83 12.86,11.763 20.572,13.156c3.67,0.663 48.948,0.829 53.585,0.197Z" style="fill:#fff;fill-rule:nonzero;"/>
<path d="M67.575,75.717c-4.123,-1.136 -5.663,-7.051 -2.633,-10.111c1.937,-1.955 2.472,-2.029 14.595,-2.029c10.883,0 11.249,0.023 12.848,0.831c2.31,1.167 3.314,2.812 3.314,5.432c0,2.367 -0.943,4.025 -3.046,5.357c-1.129,0.716 -1.804,0.76 -12.467,0.823c-6.584,0.039 -11.83,-0.087 -12.611,-0.303l0,0Z" style="fill:#f57d00;fill-rule:nonzero;"/>
<path d="M67.058,115.526c-1.769,-0.771 -3.417,-2.913 -3.702,-4.813c-0.272,-1.809 0.638,-4.296 2.032,-5.558c1.757,-1.59 2.528,-1.643 24.134,-1.66c22.227,-0.017 22.111,-0.027 24.219,1.941c2.976,2.78 2.349,7.728 -1.239,9.76l-3.686,0.6l-19.213,0.224c-16.883,0.198 -21.666,-0.111 -22.545,-0.494l0,0Z" style="fill:#f57d00;fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,3 +0,0 @@
<svg t="1657361174041" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4922" width="32" height="32">
<path d="M259.072 303.104q30.72 0 52.736 22.016t22.016 53.76q0 30.72-22.016 52.736t-52.736 22.016q-31.744 0-53.248-22.016t-21.504-52.736q0-31.744 21.504-53.76t53.248-22.016zM864.256 57.344q43.008 0 69.12 28.672t26.112 65.536l0 550.912q0 23.552-16.896 39.936t-40.448 16.384l-70.656 0 0-123.904 44.032 0q11.264 0 19.456-8.192t8.192-20.48q0-11.264-8.192-19.456t-19.456-8.192l-44.032 0 0-79.872 44.032 0q11.264 0 19.456-8.192t8.192-19.456-8.192-19.968-19.456-8.704l-44.032 0 0-72.704 44.032 0q11.264 0 19.456-8.192t8.192-20.48q0-11.264-8.192-19.456t-19.456-8.192l-44.032 0 0-86.016q0-57.344-26.624-80.896t-90.112-23.552l-394.24 0 0-9.216q0-23.552 16.896-39.936t40.448-16.384l486.4 0zM692.224 184.32q39.936 0 57.856 23.04t17.92 59.904l0 565.248q0 23.552-19.456 43.52t-48.128 19.968l-572.416 0q-24.576 0-44.032-20.48t-19.456-48.128l0-575.488q0-29.696 16.384-48.64t43.008-18.944l568.32 0zM703.488 291.84q0-17.408-10.752-30.208t-34.304-12.8l-488.448 0q-4.096 0-11.264 1.536t-14.336 5.12-12.288 9.728-5.12 15.36l0 274.432q8.192 9.216 23.04 22.016t34.816 23.552 44.544 18.432 53.248 7.68q43.008 0 75.264-13.824t59.904-34.816 54.272-45.056 58.88-45.568 73.728-36.352 98.816-16.896l0-142.336z" p-id="4923" fill="#1296db"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="32" height="32"><path d="M395.9 484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z" fill="#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z" fill="#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z" fill="#609926"/></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,5 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1652950823759" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2991" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs><style type="text/css"></style></defs>
<path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" fill="#C71D23" p-id="2992"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,3 +1,4 @@
<svg
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 58">
<g fill="none" fill-rule="evenodd">

Before

Width:  |  Height:  |  Size: 1010 B

After

Width:  |  Height:  |  Size: 1011 B

View File

@ -1,6 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 58">
<g fill="none" fill-rule="evenodd">
<path d="m1324.62 140c-16.355 0-29.616 13.219-29.616 29.527 0 13.04 8.485 24.11 20.256 28.01 1.482.27 2.02-.642 2.02-1.425 0-.7-.025-2.557-.04-5.02-8.238 1.784-9.976-3.958-9.976-3.958-1.347-3.411-3.289-4.317-3.289-4.317-2.689-1.832.204-1.796.204-1.796 2.973.21 4.536 3.043 4.536 3.043 2.642 4.511 6.931 3.208 8.62 2.454.269-1.909 1.033-3.21 1.88-3.948-6.576-.745-13.491-3.279-13.491-14.592 0-3.223 1.155-5.858 3.049-7.922-.305-.747-1.322-3.748.289-7.814 0 0 2.487-.794 8.145 3.03 2.362-.656 4.896-.982 7.415-.995 2.515.013 5.05.339 7.415.995 5.655-3.821 8.136-3.03 8.136-3.03 1.616 4.065.6 7.07.295 7.814 1.898 2.064 3.045 4.7 3.045 7.922 0 11.343-6.925 13.838-13.524 14.569 1.064.912 2.01 2.713 2.01 5.468 0 3.946-.036 7.13-.036 8.098 0 .79.533 1.709 2.036 1.421 11.758-3.913 20.238-14.971 20.238-28.01 0-16.309-13.262-29.527-29.62-29.527" transform="translate(-1295-140)" fill="#fff"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1007 B

View File

@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48">
<defs>
<path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"/>
</defs>
<clipPath id="b">
<use xlink:href="#a" overflow="visible"/>
</clipPath>
<path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"/>
<path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"/>
<path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"/>
<path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"/>
</svg>

Before

Width:  |  Height:  |  Size: 729 B

View File

@ -1,25 +0,0 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve"> <image id="image0" width="32" height="32" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB7FBMVEUAAAB3AP+IAP+IAP+H
AP+IAP+IAP+GAP+HAP+AAP+GAP+IAP+IAP+HAP+KAP+OAP+IAP+IAP+IAP9xAP+IAP+IAP+KAP+F
AP+HAP+IAP+IAP+DAP+AAP+HAP+HAP+AAP+IAP+IAP+JAP+HAP+IAP+HAP+IAP+HAP+HAP+IAP+J
AP+GAP+IAP+IAP+IAP+HAP+HAP+HAP+HAP+IAP+IAP+IAP+HAP+IAP+HAP+IAP+IAP+HAP+HAP+H
AP+DAP+HAP+DAP+HAP+IAP+IAP+JAv+mQv+8cP+9c/+sTv+MCf+iOP/s1//////05/+mQf+hNv/6
9v/37/+QE//kx//Yrf+pR/+nRP/YrP+1Yf+SF//+/v+TGP/Df/+gNP/Egf+lPv+5af+aKP/8+P+j
O//asf/27f+RFf+IAv/x4//37v/NlP/x4v+jOv+4Z//Rn/+LB/+IAf/Xqv+/eP+LBv/Wqf+7b/+3
Zv/euP+vVf/Nlv/8+v+bKf/Dgf/Ghv/Ol/+eMf/9/P+vU/+QEf/48f/Sof+UG//ctP/48P/v3v+M
CP/z5f/16/+aJ/+SFv/Zr//ozv/euf+cKv+WHv+tUP/r1P+mQP/Egv/Lkf+PEP/v3P/Jjf/duf/+
/f/fu/+kPf+2Y//Cff/Bev+xWP+TGf+sor+CAAAAQ3RSTlMAD1qczOv7nFkOJp72nSUJjfqLCdTS
IzLqL+kjCtXRCI+JJ/v5oJoR9/QNXVafmNDI8Oj++PzszJsP9SSO0iWKJ5/LDpe2AgAAAAFiS0dE
TPdvEPMAAAAHdElNRQfmBhwAJyh2NlUnAAAByUlEQVQ4y2NggANGJmYWVjY2VhZ2Dk4GTMDFzeMM
Bzy8fGjS/AKCzihAUEgYWZ5PxBkDiIoh5MVZnbEACUm4fiR5F1c3dw9PCFtKGiIvI4uQ9/L2AQJf
PwhPTh6sQAEh7x/gAwaBQRC+IkheSRmhINjHJyQ0LNzHJwLCVwFZooqQj4zyiYp0do728YmBiqgx
MKhrIBTEAk2Pc3aO9/FJgIpoajFoI3ksEaggKdk5xSc1DSakw6CLpCA9A6giM8snOwcupMegjxw2
uWA/5OUjRAwYDJEVFBSCFEQVIUSMGIxRgre4BKyiFC5gwmCKkC0rr3AuqASpqKqGiZkymMHla3x8
amG21MEEzRjMYcz6Bh+fRiDd1AxU4A0TZWFghjFbgOKtIEYbkNEOE7VgYIIxO4DinTAFXTBRDgZL
WFLsBor3AOlekBV9UEErYPLlhrL7geITJk6aPAXkz6lQQWZQcoYm12k+CDAdKi9oDUoQNhDOjJkw
6ahZLlAFQpAkB03SrbOjQNJz5s6DudAWmvTtYIm2Z/6ChYsWw0NOSozYZI8949hKI2ctGQFlVGlB
e5SsBwQOjkiZ14rZDkv+tmRiNjdjU2Z1skDO/gDtseT0Fzic2AAAACV0RVh0ZGF0ZTpjcmVhdGUA
MjAyMi0wNi0yOFQwMDozOTo0MCswMDowMPmC6NgAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjItMDYt
MjhUMDA6Mzk6NDArMDA6MDCI31BkAAAAAElFTkSuQmCC" />
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill-rule="evenodd" stroke-linejoin="round" clip-rule="evenodd" stroke-miterlimit="1.414"><path d="M 24 6 c 0 -3.3 -2.7 -6 -6 -6 H 6 C 2.7 0 0 2.7 0 6 v 12 c 0 3.3 2.7 6 6 6 h 12 c 3.3 0 6 -2.7 6 -6 V 6 z" fill="none"/><clipPath id="prefix__a"><path d="M 24 6 c 0 -3.3 -2.7 -6 -6 -6 H 6 C 2.7 0 0 2.7 0 6 v 12 c 0 3.3 2.7 6 6 6 h 12 c 3.3 0 6 -2.7 6 -6 V 6 z"/></clipPath><g clip-path="url(#prefix__a)"><path d="M 24 0 H 0 l 12 12 L 24 0 z" fill="gold"/><path d="M 0 0 v 24 l 12 -12 L 0 0 z" fill="#a5c700"/><path d="M 0 24 h 24 L 12 12 L 0 24 z" fill="#ff8a00"/><path d="M 24 24 V 0 L 12 12 l 12 12 z" fill="#66aefd"/><path d="M 22.5 -1.5 L 12 9 l 3 3 L 25.5 1.5 l -3 -3 z" fill="url(#prefix___Linear2)"/><path d="M 25.5 22.5 L 15 12 l -3 3 l 10.5 10.5 l 3 -3 z" fill="url(#prefix___Linear3)"/><path d="M 1.5 25.5 L 12 15 l -3 -3 l -10.5 10.5 l 3 3 z" fill="url(#prefix___Linear4)"/><path d="M -1.5 1.5 L 9 12 l 3 -3 L 1.5 -1.5 l -3 3 z" fill="url(#prefix___Linear5)"/></g><path d="M 21.8 5.9 c 0 -2.2 -1.8 -4 -4 -4 H 6.3 c -2.2 0 -4 1.8 -4 4 v 11.5 c 0 2.2 1.8 4 4 4 h 11.5 c 2.2 0 4 -1.8 4 -4 V 5.9 z" fill="#ffffff"/><path d="M 4.6 6 H 6 V 4.2 h 1.4 V 6 h 1.7 V 4.2 h 1.4 V 6 h 1.4 v 1.7 h -1.4 v 1.9 h 1.4 v 1.7 h -1.4 v 1.8 H 9.1 v -1.8 H 7.4 v 1.8 H 6 v -1.8 H 4.6 V 9.6 H 6 V 7.7 H 4.6 V 6 z m 2.8 1.7 v 1.9 h 1.7 V 7.7 H 7.4 z M 10 14 v 6 h 4 v -2 h -2 v -2 h 2 v -2 H 10 z m 5 0 v 6 h 2 v -3 l 1 3 h 2 v -6 h -2 v 3 l -1 -3 h -2 z M 7 18 l 0 2 l 2 0 l 0 -2 l -2 0 Z" fill="#737373"/><defs><linearGradient id="prefix___Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.99995 -3 3 -2.99995 23.997 3.003)"><stop offset="0" stop-color="#66aefd"/><stop offset="1" stop-color="gold"/></linearGradient><linearGradient id="prefix___Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3 -2.99995 2.99995 3 20.999 24.003)"><stop offset="0" stop-color="#ff8a00"/><stop offset="1" stop-color="#66aefd"/></linearGradient><linearGradient id="prefix___Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.99995 3 -3 2.99995 -.003 21.001)"><stop offset="0" stop-color="#a5c700"/><stop offset="1" stop-color="#ff8a00"/></linearGradient><linearGradient id="prefix___Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-3 2.99995 -2.99995 -3 2.997 .003)"><stop offset="0" stop-color="gold"/><stop offset="1" stop-color="#a5c700"/></linearGradient></defs></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M23.997,6.002c0,-3.311 -2.688,-5.999 -5.999,-5.999l-11.999,0c-3.311,0 -5.999,2.688 -5.999,5.999l0,11.999c0,3.311 2.688,5.999 5.999,5.999l11.999,0c3.311,0 5.999,-2.688 5.999,-5.999l0,-11.999Z" style="fill:none;"/><clipPath id="_clip1"><path d="M23.997,6.002c0,-3.311 -2.688,-5.999 -5.999,-5.999l-11.999,0c-3.311,0 -5.999,2.688 -5.999,5.999l0,11.999c0,3.311 2.688,5.999 5.999,5.999l11.999,0c3.311,0 5.999,-2.688 5.999,-5.999l0,-11.999Z"/></clipPath><g clip-path="url(#_clip1)"><path d="M23.997,0.003l-24,0l12,12l12,-12Z" style="fill:#ffd700;"/><path d="M-0.003,0.003l0,24l12,-12l-12,-12Z" style="fill:#a5c700;"/><path d="M-0.003,24.003l24,0l-12,-12l-12,12Z" style="fill:#ff8a00;"/><path d="M23.997,24.003l0,-24l-12,12l12,12Z" style="fill:#66aefd;"/><path d="M22.497,-1.497l-10.5,10.497l3,3.003l10.5,-10.5l-3,-3Z" style="fill:url(#_Linear2);"/><path d="M25.499,22.503l-10.498,-10.5l-3.002,3l10.5,10.5l3,-3Z" style="fill:url(#_Linear3);"/><path d="M1.497,25.501l10.5,-10.497l-3,-3.003l-10.5,10.5l3,3Z" style="fill:url(#_Linear4);"/><path d="M-1.503,1.503l10.498,10.5l3.002,-3l-10.5,-10.5l-3,3Z" style="fill:url(#_Linear5);"/></g><path d="M21.75,5.852c0,-2.195 -1.782,-3.977 -3.977,-3.977l-11.546,0c-2.195,0 -3.977,1.782 -3.977,3.977l0,11.546c0,2.195 1.782,3.977 3.977,3.977l11.546,0c2.195,0 3.977,-1.782 3.977,-3.977l0,-11.546Z" style="fill:#fff;"/><path d="M4.633,6.013l1.37,0l0,-1.828l1.399,0l0,1.828l1.696,0l0,-1.828l1.399,0l0,1.828l1.37,0l0,1.691l-1.37,0l0,1.902l1.37,0l0,1.69l-1.37,0l0,1.829l-1.399,0l0,-1.829l-1.696,0l0,1.829l-1.399,0l0,-1.829l-1.37,0l0,-1.69l1.37,0l0,-1.902l-1.37,0l0,-1.691Zm2.769,1.691l0,1.902l1.696,0l0,-1.902l-1.696,0Z" style="fill:#737373;"/><defs><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.99995,-3,3,-2.99995,23.9974,3.00265)"><stop offset="0" style="stop-color:#66aefd;stop-opacity:1"/><stop offset="1" style="stop-color:#ffd700;stop-opacity:1"/></linearGradient><linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3,-2.99995,2.99995,3,20.9987,24.0027)"><stop offset="0" style="stop-color:#ff8a00;stop-opacity:1"/><stop offset="1" style="stop-color:#66aefd;stop-opacity:1"/></linearGradient><linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.99995,3,-3,2.99995,-0.00255928,21.0013)"><stop offset="0" style="stop-color:#a5c700;stop-opacity:1"/><stop offset="1" style="stop-color:#ff8a00;stop-opacity:1"/></linearGradient><linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-3,2.99995,-2.99995,-3,2.99744,0.00265252)"><stop offset="0" style="stop-color:#ffd700;stop-opacity:1"/><stop offset="1" style="stop-color:#a5c700;stop-opacity:1"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -1,5 +1,5 @@
<template>
<div class="app" :class="classes" @keydown.esc="close">
<div class="app" :class="classes">
<splash-screen v-if="!ready"></splash-screen>
<layout v-else></layout>
<modal></modal>
@ -19,11 +19,10 @@ import ContextMenu from './ContextMenu';
import SplashScreen from './SplashScreen';
import syncSvc from '../services/syncSvc';
import networkSvc from '../services/networkSvc';
import sponsorSvc from '../services/sponsorSvc';
import tempFileSvc from '../services/tempFileSvc';
import store from '../store';
import './common/vueGlobals';
import utils from '../services/utils';
import providerRegistry from '../services/providers/common/providerRegistry';
const themeClasses = {
light: ['app--light'],
@ -47,53 +46,11 @@ export default {
return Array.isArray(result) ? result : themeClasses.light;
},
},
methods: {
close() {
tempFileSvc.close();
},
//
viewFileByPath(path) {
// md
if (!path) {
return;
}
const currDirNode = store.getters['explorer/selectedNodeFolder'];
if (path.slice(-3) === '.md') {
const rootNode = store.getters['explorer/rootNode'];
const node = utils.findNodeByPath(rootNode, currDirNode, path);
if (!node) {
return;
}
store.commit('explorer/setSelectedId', node.item.id);
// Prevent from freezing the UI while loading the file
setTimeout(() => {
store.commit('file/setCurrentId', node.item.id);
}, 10);
} else {
const workspace = store.getters['workspace/currentWorkspace'];
const provider = providerRegistry.providersById[workspace.providerId];
if (provider == null) {
return;
}
const absolutePath = utils.getAbsoluteFilePath(currDirNode, path);
const url = provider.getFilePathUrl(absolutePath);
if (url) {
window.open(url, '_blank');
}
}
},
},
async created() {
window.viewFileByPath = this.viewFileByPath;
try {
await syncSvc.init();
await networkSvc.init();
// store
const editTheme = localStorage.getItem('theme/currEditTheme');
store.dispatch('theme/setEditTheme', editTheme || 'default');
// store
const previewTheme = localStorage.getItem('theme/currPreviewTheme');
store.dispatch('theme/setPreviewTheme', previewTheme || 'default');
await sponsorSvc.init();
this.ready = true;
tempFileSvc.setReady();
} catch (err) {

View File

@ -1,24 +1,24 @@
<template>
<div class="button-bar">
<div class="button-bar__inner button-bar__inner--top">
<button class="button-bar__button button-bar__button--navigation-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showNavigationBar }" v-if="!light" @click="toggleNavigationBar()" v-title="'切换导航栏'">
<button class="button-bar__button button-bar__button--navigation-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showNavigationBar }" v-if="!light" @click="toggleNavigationBar()" v-title="'Toggle navigation bar'">
<icon-navigation-bar></icon-navigation-bar>
</button>
<button class="button-bar__button button-bar__button--side-preview-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showSidePreview }" tour-step-anchor="editor" @click="toggleSidePreview()" v-title="'切换侧边预览'">
<button class="button-bar__button button-bar__button--side-preview-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showSidePreview }" tour-step-anchor="editor" @click="toggleSidePreview()" v-title="'Toggle side preview'">
<icon-side-preview></icon-side-preview>
</button>
<button class="button-bar__button button-bar__button--editor-toggler button" @click="toggleEditor(false)" v-title="'阅读模式'">
<button class="button-bar__button button-bar__button--editor-toggler button" @click="toggleEditor(false)" v-title="'Reader mode'">
<icon-eye></icon-eye>
</button>
</div>
<div class="button-bar__inner button-bar__inner--bottom">
<button class="button-bar__button button-bar__button--focus-mode-toggler button" :class="{ 'button-bar__button--on': layoutSettings.focusMode }" @click="toggleFocusMode()" v-title="'切换对焦模式'">
<button class="button-bar__button button-bar__button--focus-mode-toggler button" :class="{ 'button-bar__button--on': layoutSettings.focusMode }" @click="toggleFocusMode()" v-title="'Toggle focus mode'">
<icon-target></icon-target>
</button>
<button class="button-bar__button button-bar__button--scroll-sync-toggler button" :class="{ 'button-bar__button--on': layoutSettings.scrollSync }" @click="toggleScrollSync()" v-title="'切换滚动同步'">
<button class="button-bar__button button-bar__button--scroll-sync-toggler button" :class="{ 'button-bar__button--on': layoutSettings.scrollSync }" @click="toggleScrollSync()" v-title="'Toggle scroll sync'">
<icon-scroll-sync></icon-scroll-sync>
</button>
<button class="button-bar__button button-bar__button--status-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showStatusBar }" @click="toggleStatusBar()" v-title="'切换状态栏'">
<button class="button-bar__button button-bar__button--status-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showStatusBar }" @click="toggleStatusBar()" v-title="'Toggle status bar'">
<icon-status-bar></icon-status-bar>
</button>
</div>

View File

@ -7,12 +7,11 @@ import Prism from 'prismjs';
import cledit from '../services/editor/cledit';
export default {
props: ['value', 'lang', 'disabled', 'scrollClass'],
props: ['value', 'lang', 'disabled'],
mounted() {
const preElt = this.$el;
let scrollElt = preElt;
const scrollCls = this.scrollClass || 'modal';
while (scrollElt && !scrollElt.classList.contains(scrollCls)) {
while (scrollElt && !scrollElt.classList.contains('modal')) {
scrollElt = scrollElt.parentNode;
}
if (scrollElt) {
@ -44,10 +43,6 @@ export default {
overflow: auto;
padding: 0.2em 0.4em;
.app--dark & {
caret-color: $editor-color-dark-low;
}
* {
line-height: $line-height-base;
font-size: inherit !important;

View File

@ -1,5 +1,5 @@
<template>
<div class="editor" ondrop="return false;">
<div class="editor">
<pre class="editor__inner markdown-highlighting" :style="{padding: styles.editorPadding}" :class="{monospaced: computedSettings.editor.monospacedFontOnly}"></pre>
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
<comment-list v-if="styles.editorGutterWidth"></comment-list>
@ -13,9 +13,6 @@ import { mapGetters } from 'vuex';
import CommentList from './gutters/CommentList';
import EditorNewDiscussionButton from './gutters/EditorNewDiscussionButton';
import store from '../store';
import editorSvc from '../services/editorSvc';
import imageSvc from '../services/imageSvc';
import utils from '../services/utils';
export default {
components: {
@ -33,51 +30,7 @@ export default {
'computedSettings',
]),
},
methods: {
async processUpload(items) {
let file = null;
if (!items || items.length === 0) {
return;
}
for (let i = 0; i < items.length; i += 1) {
if (items[i].type.indexOf('image') !== -1) {
file = items[i].getAsFile();
break;
}
}
if (!file) {
return;
}
const imgId = utils.uid();
store.dispatch('img/setCurrImgId', imgId);
editorSvc.pagedownEditor.uiManager.doClick('imageUploading');
try {
const { url, error } = await imageSvc.updateImg(file);
//
if (error) {
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `[图片上传失败...(image-${imgId})]`);
store.dispatch('notification/error', error);
return;
}
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `![输入图片说明](${url})`);
} catch (err) {
console.error(err); // eslint-disable-line no-console
editorSvc.clEditor.replaceAll(`[图片上传中...(image-${imgId})]`, `[图片上传失败...(image-${imgId})]`);
store.dispatch('notification/error', err);
}
},
},
mounted() {
//
const currImgStorageStr = localStorage.getItem('img/checkedStorage');
if (currImgStorageStr) {
store.commit('img/changeCheckedStorage', JSON.parse(currImgStorageStr));
}
//
const workspaceImgPath = localStorage.getItem('img/workspaceImgPath');
if (workspaceImgPath) {
store.commit('img/setWorkspaceImgPath', JSON.parse(workspaceImgPath));
}
const editorElt = this.$el.querySelector('.editor__inner');
const onDiscussionEvt = cb => (evt) => {
let elt = evt.target;
@ -103,15 +56,6 @@ export default {
store.commit('discussion/setCurrentDiscussionId', discussionId);
}));
editorElt.addEventListener('drop', (event) => {
const transItems = event.dataTransfer.items;
this.processUpload(transItems);
});
editorElt.addEventListener('paste', (event) => {
const pasteItems = (event.clipboardData || window.clipboardData).items;
this.processUpload(pasteItems);
});
this.$watch(
() => store.state.discussion.currentDiscussionId,
(discussionId, oldDiscussionId) => {

View File

@ -1,197 +0,0 @@
<template>
<div class="editor-in-page-buttons">
<ul>
<li :title="`查找 ${mod}+F`">
<a @click="showFind"><icon-search></icon-search></a>
</li>
<li :title="`替换 ${mod}+Alt+F`">
<a @click="showFindReplace"><icon-find-replace></icon-find-replace></a>
</li>
<li title="切换编辑主题">
<dropdown-menu :selected="selectedTheme" :options="allThemes" :closeOnItemClick="false" @change="changeTheme">
<icon-select-theme></icon-select-theme>
</dropdown-menu>
</li>
<li class="after">
<icon-ellipsis></icon-ellipsis>
</li>
</ul>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import store from '../store';
import editorSvc from '../services/editorSvc';
import DropdownMenu from './common/DropdownMenu';
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
export default {
components: {
DropdownMenu,
},
data: () => ({
mod,
allThemes: [{
name: '默认主题',
value: 'default',
}, {
name: '天蓝黑',
value: 'azure',
}, {
name: '冰山黑',
value: 'iceberg_contrast',
}, {
name: '黎明白',
value: 'dawn',
}, {
name: '孔雀黑',
value: 'peacock',
}, {
name: '薄荷黑',
value: 'mintchoc',
}, {
name: '薄荷绿',
value: 'spearmint',
}, {
name: '暗蓝黑',
value: 'slate',
}, {
name: '文墨黑',
value: 'carbonight',
}, {
name: '日光白',
value: 'solarized_light',
}, {
name: '咖啡黑',
value: 'espresso_libre',
}, {
name: '薰衣草黑',
value: 'lavender',
}, {
name: '耀斑黑',
value: 'solarflare',
}, {
name: 'Clouds白',
value: 'clouds',
}, {
name: 'Clouds黑',
value: 'clouds_midnight',
}, {
name: 'GitHub白',
value: 'github',
}, {
name: '自定义',
value: 'custom',
}],
}),
computed: {
...mapGetters('theme', [
'currEditTheme',
'customEditThemeStyle',
]),
selectedTheme() {
return {
value: this.currEditTheme || 'default',
};
},
},
methods: {
...mapActions('data', [
'toggleSideBar',
]),
showFind() {
store.dispatch('findReplace/open', {
type: 'find',
findText: editorSvc.clEditor.selectionMgr.hasFocus() &&
editorSvc.clEditor.selectionMgr.getSelectedText(),
});
},
showFindReplace() {
store.dispatch('findReplace/open', {
type: 'replace',
findText: editorSvc.clEditor.selectionMgr.hasFocus() &&
editorSvc.clEditor.selectionMgr.getSelectedText(),
});
},
async changeTheme(item) {
await store.dispatch('theme/setEditTheme', item.value);
//
if (item.value === 'custom' && !this.customEditThemeStyle) {
this.toggleSideBar(true);
store.dispatch('data/setSideBarPanel', 'editTheme');
}
},
},
};
</script>
<style lang="scss">
@import '../styles/variables.scss';
.editor-in-page-buttons {
position: absolute;
top: 0;
left: -108px;
height: 34px;
padding: 5px;
background-color: rgba(84, 96, 114, 0.4);
border-radius: $border-radius-base;
transition: 0.5s;
display: flex;
.dropdown-menu {
display: none;
.dropdown-menu-items {
right: unset;
left: 0;
}
}
&:active,
&:focus,
&:hover {
left: 0;
transition: 0.5s;
background-color: #546072;
.dropdown-menu {
display: block;
}
}
ul {
padding: 0;
margin-left: 10px;
line-height: 20px;
li {
width: 16px;
display: inline-block;
vertical-align: middle;
list-style: none;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
}
}
.icon {
color: #dea731;
opacity: 0.7;
&:active,
&:focus,
&:hover {
opacity: 1;
}
}
.after {
margin-left: 0;
margin-right: -6px;
}
}
</style>

View File

@ -1,49 +1,27 @@
<template>
<div class="explorer flex flex--column">
<div class="side-title flex flex--row flex--space-between">
<div class="flex flex--row" v-if="!showSearch">
<button class="side-title__button side-title__button--new-file button" @click="newItem()" v-title="'创建文件'">
<div class="flex flex--row">
<button class="side-title__button side-title__button--new-file button" @click="newItem()" v-title="'New file'">
<icon-file-plus></icon-file-plus>
</button>
<button class="side-title__button side-title__button--new-folder button" @click="newItem(true)" v-title="'创建文件夹'">
<button class="side-title__button side-title__button--new-folder button" @click="newItem(true)" v-title="'New folder'">
<icon-folder-plus></icon-folder-plus>
</button>
<button class="side-title__button side-title__button--delete button" @click="deleteItem()" v-title="'删除'">
<button class="side-title__button side-title__button--delete button" @click="deleteItem()" v-title="'Delete'">
<icon-delete></icon-delete>
</button>
<button class="side-title__button side-title__button--rename button" @click="editItem()" v-title="'重命名'">
<button class="side-title__button side-title__button--rename button" @click="editItem()" v-title="'Rename'">
<icon-pen></icon-pen>
</button>
<button class="side-title__button side-title__button--search button" @click="toSearch()" v-title="'搜索文件'">
<icon-file-search></icon-file-search>
</button>
</div>
<div class="flex flex--row" v-else>
<button class="side-title__button button" @click="back()" v-title="'返回资源管理器'">
<icon-dots-horizontal></icon-dots-horizontal>
</button>
<div class="side-title__title">
搜索文件
</div>
</div>
<button class="side-title__button side-title__button--close button" @click="toggleExplorer(false)" v-title="'关闭资源管理器'">
<button class="side-title__button side-title__button--close button" @click="toggleExplorer(false)" v-title="'Close explorer'">
<icon-close></icon-close>
</button>
</div>
<div class="explorer__tree" :class="{'explorer__tree--new-item': !newChildNode.isNil}" v-if="!light" v-show="!showSearch" tabindex="0" @keydown.delete="deleteItem()">
<div class="explorer__tree" :class="{'explorer__tree--new-item': !newChildNode.isNil}" v-if="!light" tabindex="0" @keydown.delete="deleteItem()">
<explorer-node :node="rootNode" :depth="0"></explorer-node>
</div>
<div class="explorer__search" tabindex="0" v-if="!light && showSearch">
<input type="text" v-model="searchText" class="text-input" placeholder="请输入关键字回车" @keyup.enter="search" />
<div class="explorer__search-list">
<div class="search-tips" v-if="searching">正在查询中...</div>
<a class="menu-entry button flex flex--row flex--align-center" :class="{'search-node--selected': currentFileId === fileItem.id}"
v-for="fileItem in searchItems" :key="fileItem.id" @click="clickSearch(fileItem)" href="javascript:void(0)">
{{ fileItem.name }}
</a>
<div class="search-tips">最多返回匹配的50个文档</div>
</div>
</div>
</div>
</template>
@ -52,22 +30,11 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import ExplorerNode from './ExplorerNode';
import explorerSvc from '../services/explorerSvc';
import store from '../store';
import MenuEntry from './menus/common/MenuEntry';
import localDbSvc from '../services/localDbSvc';
import badgeSvc from '../services/badgeSvc';
export default {
components: {
ExplorerNode,
MenuEntry,
},
data: () => ({
currentFileId: '',
showSearch: false,
searching: false,
searchText: '',
searchItems: [],
}),
computed: {
...mapState([
'light',
@ -79,7 +46,6 @@ export default {
'rootNode',
'selectedNode',
]),
workspaceId: () => store.getters['workspace/currentWorkspace'].id,
},
methods: {
...mapActions('data', [
@ -93,69 +59,11 @@ export default {
store.commit('explorer/setEditingId', node.item.id);
}
},
back() {
this.showSearch = false;
},
toSearch() {
this.showSearch = true;
},
search() {
this.searchItems = [];
if (!this.searchText) {
return;
}
this.searching = true;
const allFileById = {};
const filterIds = [];
localDbSvc.getWorkspaceItems(this.workspaceId, (item) => {
if (item.type !== 'file' && item.type !== 'content') {
return;
}
if (item.type === 'file') {
allFileById[item.id] = item;
}
if (filterIds.length >= 50) {
return;
}
const fileId = item.id.split('/')[0];
//
if (filterIds.indexOf(fileId) > -1) {
return;
}
if (item.name && item.name.indexOf(this.searchText) > -1) {
filterIds.push(fileId);
}
if (item.text && item.text.indexOf(this.searchText) > -1) {
filterIds.push(fileId);
}
}, () => {
filterIds.forEach((it) => {
const file = allFileById[it];
if (file) {
this.searchItems.push(file);
}
});
this.searching = false;
badgeSvc.addBadge('searchFile');
});
},
clickSearch(item) {
const node = store.getters['explorer/nodeMap'][item.id];
if (!node) {
return;
}
store.commit('explorer/setSelectedId', item.id);
// Prevent from freezing the UI while loading the file
setTimeout(() => {
store.commit('file/setCurrentId', item.id);
}, 10);
},
},
created() {
this.$watch(
() => store.getters['file/current'].id,
(currentFileId) => {
this.currentFileId = currentFileId;
store.commit('explorer/setSelectedId', currentFileId);
store.dispatch('explorer/openNode', currentFileId);
}, {
@ -167,8 +75,6 @@ export default {
</script>
<style lang="scss">
@import '../styles/variables.scss';
.explorer,
.explorer__tree {
height: 100%;
@ -183,43 +89,4 @@ export default {
cursor: auto;
}
}
.explorer__search {
overflow: auto;
.explorer__search-list {
margin-top: 10px;
}
.menu-entry {
font-size: 14px;
padding: 5px;
}
.menu-entry__icon {
width: 0;
margin-left: 0;
border-bottom: 1px solid $hr-color;
}
.search-tips {
font-size: 10px;
background-color: rgba(255, 173, 51, 0.14902);
padding: 5px;
text-align: center;
}
.search-node--selected {
background-color: rgba(0, 0, 0, 0.2);
.app--dark & {
background-color: rgba(0, 0, 0, 0.4);
}
&:focus {
background-color: #39f;
color: #fff;
}
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--folder': node.isFolder, 'explorer-node--open': isOpen, 'explorer-node--trash': node.isTrash, 'explorer-node--temp': node.isTemp, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="node.noDrop || setDragTarget(node)" @dragleave.stop="isDragTarget && setDragTarget()" @drop.prevent.stop="onDrop" @contextmenu="onContextMenu">
<div class="explorer-node__item-editor" v-if="isEditing" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.esc.stop="submitEdit(true)" v-model="editingNodeName">
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.esc="submitEdit(true)" v-model="editingNodeName">
</div>
<div class="explorer-node__item" v-else :style="{paddingLeft: leftPadding}" @click="select()" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTarget()">
{{node.item.name}}
@ -10,11 +10,10 @@
<div class="explorer-node__children" v-if="node.isFolder && isOpen">
<explorer-node v-for="node in node.folders" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
<div v-if="newChild" class="explorer-node__new-child" :class="{'explorer-node__new-child--folder': newChild.isFolder}" :style="{paddingLeft: childLeftPadding}">
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc.stop="submitNewChild(true)" v-model.trim="newChildName">
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc="submitNewChild(true)" v-model.trim="newChildName">
</div>
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
</div>
<button ref="copyId" v-clipboard="copyPath()" @click="info('路径已复制到剪切板!')" style="display: none;"></button>
</div>
</template>
@ -23,8 +22,6 @@ import { mapMutations, mapActions } from 'vuex';
import workspaceSvc from '../services/workspaceSvc';
import explorerSvc from '../services/explorerSvc';
import store from '../store';
import badgeSvc from '../services/badgeSvc';
import utils from '../services/utils';
export default {
name: 'explorer-node', // Required for recursivity
@ -82,9 +79,6 @@ export default {
...mapActions('explorer', [
'setDragTarget',
]),
...mapActions('notification', [
'info',
]),
select(id = this.node.item.id, doOpen = true) {
const node = store.getters['explorer/nodeMap'][id];
if (!node) {
@ -96,9 +90,8 @@ export default {
setTimeout(() => {
if (node.isFolder) {
store.commit('explorer/toggleOpenNode', id);
} else if (store.state.file.currentId !== id) {
} else {
store.commit('file/setCurrentId', id);
badgeSvc.addBadge('switchFile');
}
}, 10);
}
@ -111,11 +104,9 @@ export default {
if (newChildNode.isFolder) {
const item = await workspaceSvc.storeItem(newChildNode.item);
this.select(item.id);
badgeSvc.addBadge('createFolder');
} else {
const item = await workspaceSvc.createFile(newChildNode.item);
this.select(item.id);
badgeSvc.addBadge('createFile');
}
} catch (e) {
// Cancel
@ -124,16 +115,15 @@ export default {
store.commit('explorer/setNewItem', null);
},
async submitEdit(cancel) {
const { item, isFolder } = store.getters['explorer/editingNode'];
const { item } = store.getters['explorer/editingNode'];
const value = this.editingValue;
this.setEditingId(null);
if (!cancel && item.id && value && item.name !== value) {
if (!cancel && item.id && value) {
try {
await workspaceSvc.storeItem({
...item,
name: value,
});
badgeSvc.addBadge(isFolder ? 'renameFolder' : 'renameFile');
} catch (e) {
// Cancel
}
@ -149,11 +139,6 @@ export default {
// See https://stackoverflow.com/a/3977637/1333165
evt.dataTransfer.setData('Text', '');
},
copyPath() {
let path = utils.getAbsoluteDir(this.node).replaceAll(' ', '%20');
path = path.indexOf('/') === 0 ? path : `/${path}`;
return this.node.isFolder ? path : `${path}.md`;
},
onDrop() {
const sourceNode = store.getters['explorer/dragSourceNode'];
const targetNode = store.getters['explorer/dragTargetNodeFolder'];
@ -166,7 +151,6 @@ export default {
...sourceNode.item,
parentId: targetNode.item.id,
});
badgeSvc.addBadge(sourceNode.isFolder ? 'moveFolder' : 'moveFile');
}
},
async onContextMenu(evt) {
@ -179,26 +163,22 @@ export default {
top: evt.clientY,
},
items: [{
name: '新建文件',
name: 'New file',
disabled: !this.node.isFolder || this.node.isTrash,
perform: () => explorerSvc.newItem(false),
}, {
name: '新建文件夹',
name: 'New folder',
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
perform: () => explorerSvc.newItem(true),
}, {
type: 'separator',
}, {
name: '重命名',
name: 'Rename',
disabled: this.node.isTrash || this.node.isTemp,
perform: () => this.setEditingId(this.node.item.id),
}, {
name: '删除',
name: 'Delete',
perform: () => explorerSvc.deleteItem(),
}, {
name: '复制路径',
disabled: this.node.isTrash || this.node.isTemp,
perform: () => this.$refs.copyId.click(),
}],
});
if (item) {
@ -229,10 +209,6 @@ $item-font-size: 14px;
.explorer-node--selected > & {
background-color: rgba(0, 0, 0, 0.2);
.app--dark & {
background-color: rgba(0, 0, 0, 0.4);
}
.explorer__tree:focus & {
background-color: #39f;
color: #fff;
@ -254,10 +230,6 @@ $item-font-size: 14px;
.explorer-node--trash,
.explorer-node--temp {
color: rgba(0, 0, 0, 0.5);
.app--dark & {
color: rgba(255, 255, 255, 0.5);
}
}
.explorer-node--folder > .explorer-node__item,

View File

@ -1,6 +1,6 @@
<template>
<div class="find-replace" @keydown.esc.stop="onEscape">
<button class="find-replace__close-button button not-tabbable" @click="close()" v-title="'关闭'">
<div class="find-replace" @keydown.esc="onEscape">
<button class="find-replace__close-button button not-tabbable" @click="close()" v-title="'Close'">
<icon-close></icon-close>
</button>
<div class="find-replace__row">
@ -14,8 +14,8 @@
<button class="find-replace__button find-replace__button--find-option button" :class="{'find-replace__button--on': findUseRegexp}" @click="findUseRegexp = !findUseRegexp" title="Regular expression">.<sup></sup></button>
</div>
<div class="flex flex--row">
<button class="find-replace__button button" @click="find('backward')">上一个</button>
<button class="find-replace__button button" @click="find('forward')">下一个</button>
<button class="find-replace__button button" @click="find('backward')">Previous</button>
<button class="find-replace__button button" @click="find('forward')">Next</button>
</div>
</div>
</div>
@ -24,8 +24,8 @@
<input type="text" class="find-replace__text-input find-replace__text-input--replace text-input" @keydown.enter="replace" v-model="replaceText">
</div>
<div class="find-replace__row flex flex--row flex--end">
<button class="find-replace__button button" @click="replace">替换</button>
<button class="find-replace__button button" @click="replaceAll">全部替换</button>
<button class="find-replace__button button" @click="replace">Replace</button>
<button class="find-replace__button button" @click="replaceAll">All</button>
</div>
</div>
</div>
@ -307,36 +307,20 @@ export default {
color: rgba(0, 0, 0, 0.25);
text-transform: none;
.app--dark & {
color: rgba(255, 255, 255, 0.25);
}
&:active,
&:focus,
&:hover {
color: rgba(0, 0, 0, 0.25);
.app--dark & {
color: rgba(255, 255, 255, 0.25);
}
}
}
.find-replace__button--on {
color: rgba(0, 0, 0, 0.67);
.app--dark & {
color: rgba(255, 255, 255, 0.67);
}
&:active,
&:focus,
&:hover {
color: rgba(0, 0, 0, 0.67);
.app--dark & {
color: rgba(255, 255, 255, 0.67);
}
}
}
@ -359,18 +343,10 @@ export default {
padding: 2px;
color: rgba(0, 0, 0, 0.5);
.app--dark & {
color: rgba(255, 255, 255, 0.5);
}
&:active,
&:focus,
&:hover {
color: rgba(0, 0, 0, 0.75);
.app--dark & {
color: rgba(255, 255, 255, 0.75);
}
}
}
@ -383,10 +359,6 @@ export default {
.find-replace-highlighting {
background-color: $highlighting-color;
color: $editor-color-light !important;
.app--dark & {
background-color: $dark-highlighting-color;
}
}
.find-replace-selection {

View File

@ -9,12 +9,11 @@
<navigation-bar></navigation-bar>
</div>
<div class="layout__panel flex flex--row" :style="{height: styles.innerHeight + 'px'}">
<div class="layout__panel layout__panel--editor" :class="editTheme" v-show="styles.showEditor" :style="{width: (styles.editorWidth + styles.editorGutterWidth) + 'px', fontSize: styles.fontSize + 'px'}">
<div class="layout__panel layout__panel--editor" v-show="styles.showEditor" :style="{width: (styles.editorWidth + styles.editorGutterWidth) + 'px', fontSize: styles.fontSize + 'px'}">
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
<div class="gutter__background" v-if="styles.editorGutterWidth" :style="{width: styles.editorGutterWidth + 'px'}"></div>
</div>
<editor></editor>
<editor-in-page-buttons v-if="editorShowInPageButtons"></editor-in-page-buttons>
<div class="gutter" :style="{left: styles.editorGutterLeft + 'px'}">
<sticky-comment v-if="styles.editorGutterWidth && stickyComment === 'top'"></sticky-comment>
<current-discussion v-if="styles.editorGutterWidth"></current-discussion>
@ -28,7 +27,6 @@
<div class="gutter__background" v-if="styles.previewGutterWidth" :style="{width: styles.previewGutterWidth + 'px'}"></div>
</div>
<preview></preview>
<preview-in-page-buttons></preview-in-page-buttons>
<div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}">
<sticky-comment v-if="styles.previewGutterWidth && stickyComment === 'top'"></sticky-comment>
<current-discussion v-if="styles.previewGutterWidth"></current-discussion>
@ -60,8 +58,6 @@ import SideBar from './SideBar';
import Editor from './Editor';
import Preview from './Preview';
import Tour from './Tour';
import EditorInPageButtons from './EditorInPageButtons';
import PreviewInPageButtons from './PreviewInPageButtons';
import StickyComment from './gutters/StickyComment';
import CurrentDiscussion from './gutters/CurrentDiscussion';
import FindReplace from './FindReplace';
@ -79,8 +75,6 @@ export default {
Editor,
Preview,
Tour,
EditorInPageButtons,
PreviewInPageButtons,
StickyComment,
CurrentDiscussion,
FindReplace,
@ -102,18 +96,9 @@ export default {
...mapGetters('data', [
'layoutSettings',
]),
...mapGetters('theme', [
'currEditTheme',
]),
editTheme() {
return `edit-theme--${this.currEditTheme || 'default'}`;
},
showFindReplace() {
return !!store.state.findReplace.type;
},
editorShowInPageButtons() {
return store.getters['data/computedSettings'].editor.showInPageButtons;
},
},
methods: {
...mapActions('layout', [
@ -192,15 +177,17 @@ export default {
.sticky-comment,
.current-discussion {
background-color: mix(#000, $editor-background-light, 6.7%);
border-color: $editor-background-light;
.app--dark & {
background-color: mix(#fff, $editor-background-dark, 6.7%);
border-color: $editor-background-dark;
}
}
}
$preview-background-light: #f3f3f3;
$preview-background-dark: #444;
$preview-background-dark: #252525;
.layout__panel--preview,
.layout__panel--button-bar {
@ -217,16 +204,13 @@ $preview-background-dark: #444;
.sticky-comment,
.current-discussion {
background-color: mix(#000, $preview-background-light, 6.7%);
border-color: $preview-background-light;
}
}
.layout__panel--explorer,
.layout__panel--side-bar {
background-color: #ddd;
.app--dark & {
background-color: #383c4a;
}
}
.layout__panel--find-replace {
@ -237,9 +221,5 @@ $preview-background-dark: #444;
width: 300px;
height: auto;
border-top-right-radius: $border-radius-base;
.app--dark & {
background-color: #4d5160;
}
}
</style>

View File

@ -1,16 +1,15 @@
<template>
<div class="modal" v-if="config" @keydown.esc.stop="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
<!-- <div class="modal__sponsor-banner" v-if="!isSponsor">
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/mafgwo/stackedit/">open source</a>, please consider
<div class="modal" v-if="config" @keydown.esc="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
<div class="modal__sponsor-banner" v-if="!isSponsor">
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/benweet/stackedit/">open source</a>, please consider
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
</div> -->
</div>
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
<modal-inner v-else aria-label="Dialog">
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
<div class="modal__button-bar">
<button class="button" v-if="simpleModal.rejectText" @click="config.reject()">{{simpleModal.rejectText}}</button>
<button class="button button--resolve" v-if="simpleModal.resolveText" @click="config.resolve()">{{simpleModal.resolveText}}</button>
<button v-for="(item, idx) in (simpleModal.resolveArray || [])" class="button button--resolve" @click="config.resolve(item.value)">{{item.text}}</button>
</div>
</modal-inner>
</div>
@ -21,7 +20,7 @@ import { mapGetters } from 'vuex';
import simpleModals from '../data/simpleModals';
import editorSvc from '../services/editorSvc';
import syncSvc from '../services/syncSvc';
import giteeHelper from '../services/providers/helpers/giteeHelper';
import googleHelper from '../services/providers/helpers/googleHelper';
import store from '../store';
import ModalInner from './modals/common/ModalInner';
@ -37,12 +36,7 @@ import ImageModal from './modals/ImageModal';
import SyncManagementModal from './modals/SyncManagementModal';
import PublishManagementModal from './modals/PublishManagementModal';
import WorkspaceManagementModal from './modals/WorkspaceManagementModal';
import AccountManagementModal from './modals/AccountManagementModal';
import BadgeManagementModal from './modals/BadgeManagementModal';
import SponsorModal from './modals/SponsorModal';
import CommitMessageModal from './modals/CommitMessageModal';
import WorkspaceImgPathModal from './modals/WorkspaceImgPathModal';
import ChatGptModal from './modals/ChatGptModal';
// Providers
import GooglePhotoModal from './modals/providers/GooglePhotoModal';
@ -58,27 +52,13 @@ import GithubOpenModal from './modals/providers/GithubOpenModal';
import GithubSaveModal from './modals/providers/GithubSaveModal';
import GithubWorkspaceModal from './modals/providers/GithubWorkspaceModal';
import GithubPublishModal from './modals/providers/GithubPublishModal';
import GithubImgStorageModal from './modals/providers/GithubImgStorageModal';
import GistSyncModal from './modals/providers/GistSyncModal';
import GistPublishModal from './modals/providers/GistPublishModal';
import GiteeAccountModal from './modals/providers/GiteeAccountModal';
import GiteeOpenModal from './modals/providers/GiteeOpenModal';
import GiteeSaveModal from './modals/providers/GiteeSaveModal';
import GiteeWorkspaceModal from './modals/providers/GiteeWorkspaceModal';
import GiteePublishModal from './modals/providers/GiteePublishModal';
import GiteeGistSyncModal from './modals/providers/GiteeGistSyncModal';
import GiteeGistPublishModal from './modals/providers/GiteeGistPublishModal';
import GitlabAccountModal from './modals/providers/GitlabAccountModal';
import GitlabOpenModal from './modals/providers/GitlabOpenModal';
import GitlabPublishModal from './modals/providers/GitlabPublishModal';
import GitlabSaveModal from './modals/providers/GitlabSaveModal';
import GitlabWorkspaceModal from './modals/providers/GitlabWorkspaceModal';
import GiteaAccountModal from './modals/providers/GiteaAccountModal';
import GiteaOpenModal from './modals/providers/GiteaOpenModal';
import GiteaPublishModal from './modals/providers/GiteaPublishModal';
import GiteaSaveModal from './modals/providers/GiteaSaveModal';
import GiteaWorkspaceModal from './modals/providers/GiteaWorkspaceModal';
import GiteaImgStorageModal from './modals/providers/GiteaImgStorageModal';
import WordpressPublishModal from './modals/providers/WordpressPublishModal';
import BloggerPublishModal from './modals/providers/BloggerPublishModal';
import BloggerPagePublishModal from './modals/providers/BloggerPagePublishModal';
@ -86,8 +66,6 @@ import ZendeskAccountModal from './modals/providers/ZendeskAccountModal';
import ZendeskPublishModal from './modals/providers/ZendeskPublishModal';
import CouchdbWorkspaceModal from './modals/providers/CouchdbWorkspaceModal';
import CouchdbCredentialsModal from './modals/providers/CouchdbCredentialsModal';
import SmmsAccountModal from './modals/providers/SmmsAccountModal';
import CustomAccountModal from './modals/providers/CustomAccountModal';
const getTabbables = container => container.querySelectorAll('a[href], button, .textfield, input[type=checkbox]')
// Filter enabled and visible element
@ -108,12 +86,7 @@ export default {
SyncManagementModal,
PublishManagementModal,
WorkspaceManagementModal,
AccountManagementModal,
BadgeManagementModal,
SponsorModal,
CommitMessageModal,
WorkspaceImgPathModal,
ChatGptModal,
// Providers
GooglePhotoModal,
GoogleDriveAccountModal,
@ -128,27 +101,13 @@ export default {
GithubSaveModal,
GithubWorkspaceModal,
GithubPublishModal,
GithubImgStorageModal,
GistSyncModal,
GistPublishModal,
GiteeAccountModal,
GiteeOpenModal,
GiteeSaveModal,
GiteeWorkspaceModal,
GiteePublishModal,
GiteeGistSyncModal,
GiteeGistPublishModal,
GitlabAccountModal,
GitlabOpenModal,
GitlabPublishModal,
GitlabSaveModal,
GitlabWorkspaceModal,
GiteaAccountModal,
GiteaOpenModal,
GiteaPublishModal,
GiteaSaveModal,
GiteaWorkspaceModal,
GiteaImgStorageModal,
WordpressPublishModal,
BloggerPublishModal,
BloggerPagePublishModal,
@ -156,8 +115,6 @@ export default {
ZendeskPublishModal,
CouchdbWorkspaceModal,
CouchdbCredentialsModal,
SmmsAccountModal,
CustomAccountModal,
},
computed: {
...mapGetters([
@ -187,8 +144,7 @@ export default {
if (!store.getters['workspace/sponsorToken']) {
// User has to sign in
await store.dispatch('modal/open', 'signInForSponsorship');
await giteeHelper.signin();
await syncSvc.afterSignIn();
await googleHelper.signin();
syncSvc.requestSync();
}
if (!store.getters.isSponsor) {
@ -285,10 +241,6 @@ export default {
position: relative;
overflow: hidden;
.app--dark & {
background-color: #383c4a;
}
&::before {
content: '';
position: absolute;

View File

@ -2,14 +2,13 @@
<nav class="navigation-bar" :class="{'navigation-bar--editor': styles.showEditor && !revisionContent, 'navigation-bar--light': light}">
<!-- Explorer -->
<div class="navigation-bar__inner navigation-bar__inner--left navigation-bar__inner--button">
<button class="navigation-bar__button navigation-bar__button--close button" v-if="light" @click="close()" v-title="'关闭StackEdit'"><icon-check-circle></icon-check-circle></button>
<button class="navigation-bar__button navigation-bar__button--explorer-toggler button" v-else tour-step-anchor="explorer" @click="toggleExplorer()" v-title="'切换资源管理器'"><icon-folder></icon-folder></button>
<button class="navigation-bar__button button" v-if="light" @click="close()" v-title="'Close StackEdit'"><icon-close-circle></icon-close-circle></button>
<button class="navigation-bar__button navigation-bar__button--explorer-toggler button" v-else tour-step-anchor="explorer" @click="toggleExplorer()" v-title="'Toggle explorer'"><icon-folder></icon-folder></button>
</div>
<!-- Side bar -->
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--button">
<button class="navigation-bar__button navigation-bar__button--theme button" v-title="'切换主题'" tour-step-anchor="theme" @click="switchTheme"><icon-switch-theme></icon-switch-theme></button>
<a class="navigation-bar__button navigation-bar__button--stackedit button" v-if="light" href="app" target="_blank" v-title="'打开StackEdit'"><icon-provider provider-id="stackedit"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--stackedit button" v-else tour-step-anchor="menu" @click="toggleSideBar()" v-title="'切换侧边栏'"><icon-provider provider-id="stackedit"></icon-provider></button>
<a class="navigation-bar__button navigation-bar__button--stackedit button" v-if="light" href="app" target="_blank" v-title="'Open StackEdit'"><icon-provider provider-id="stackedit"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--stackedit button" v-else tour-step-anchor="menu" @click="toggleSideBar()" v-title="'Toggle side bar'"><icon-provider provider-id="stackedit"></icon-provider></button>
</div>
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--title flex flex--row">
<!-- Spinner -->
@ -20,23 +19,23 @@
<!-- Title -->
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
<div class="navigation-bar__title navigation-bar__title--text text-input" :style="{width: titleWidth + 'px'}">{{title}}</div>
<input class="navigation-bar__title navigation-bar__title--input text-input" :class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" :style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keydown.enter="submitTitle(false)" @keydown.esc.stop="submitTitle(true)" @mouseenter="titleHover = true" @mouseleave="titleHover = false" v-model="title">
<input class="navigation-bar__title navigation-bar__title--input text-input" :class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" :style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keydown.enter="submitTitle(false)" @keydown.esc="submitTitle(true)" @mouseenter="titleHover = true" @mouseleave="titleHover = false" v-model="title">
<!-- Sync/Publish -->
<div class="flex flex--row" :class="{'navigation-bar__hidden': styles.hideLocations}">
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in syncLocations" :key="location.id" :href="location.url" target="_blank" v-title="'同步位置'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--sync button" :disabled="!isSyncPossible || isSyncRequested || offline" @click="requestSync" v-title="'立即同步'"><icon-sync></icon-sync></button>
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in publishLocations" :key="location.id" :href="location.url" target="_blank" v-title="'发布位置'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--publish button" :disabled="!publishLocations.length || isPublishRequested || offline" @click="requestPublish" v-title="'立即发布'"><icon-upload></icon-upload></button>
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in syncLocations" :key="location.id" :href="location.url" target="_blank" v-title="'Synchronized location'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--sync button" :disabled="!isSyncPossible || isSyncRequested || offline" @click="requestSync" v-title="'Synchronize now'"><icon-sync></icon-sync></button>
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in publishLocations" :key="location.id" :href="location.url" target="_blank" v-title="'Publish location'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
<button class="navigation-bar__button navigation-bar__button--publish button" :disabled="!publishLocations.length || isPublishRequested || offline" @click="requestPublish" v-title="'Publish now'"><icon-upload></icon-upload></button>
</div>
<!-- Revision -->
<div class="flex flex--row" v-if="revisionContent">
<button class="navigation-bar__button navigation-bar__button--revision navigation-bar__button--restore button" @click="restoreRevision">恢复</button>
<button class="navigation-bar__button navigation-bar__button--revision button" @click="setRevisionContent()" v-title="'关闭修订'"><icon-close></icon-close></button>
<button class="navigation-bar__button navigation-bar__button--revision navigation-bar__button--restore button" @click="restoreRevision">Restore</button>
<button class="navigation-bar__button navigation-bar__button--revision button" @click="setRevisionContent()" v-title="'Close revision'"><icon-close></icon-close></button>
</div>
</div>
<div class="navigation-bar__inner navigation-bar__inner--edit-pagedownButtons">
<button class="navigation-bar__button button" @click="undo" v-title="'回退'" :disabled="!canUndo"><icon-undo></icon-undo></button>
<button class="navigation-bar__button button" @click="redo" v-title="'重做'" :disabled="!canRedo"><icon-redo></icon-redo></button>
<button class="navigation-bar__button button" @click="undo" v-title="'Undo'" :disabled="!canUndo"><icon-undo></icon-undo></button>
<button class="navigation-bar__button button" @click="redo" v-title="'Redo'" :disabled="!canRedo"><icon-redo></icon-redo></button>
<div v-for="button in pagedownButtons" :key="button.method">
<button class="navigation-bar__button button" v-if="button.method" @click="pagedownClick(button.method)" v-title="button.titleWithShortcut">
<component :is="button.iconClass"></component>
@ -58,7 +57,6 @@ import utils from '../services/utils';
import pagedownButtons from '../data/pagedownButtons';
import store from '../store';
import workspaceSvc from '../services/workspaceSvc';
import badgeSvc from '../services/badgeSvc';
// According to mousetrap
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
@ -114,8 +112,7 @@ export default {
publishLocations: 'current',
}),
pagedownButtons() {
const buttonShowObj = store.getters['data/computedSettings'].editor.headButtons;
return pagedownButtons.filter(it => buttonShowObj[it.method]).map(button => ({
return pagedownButtons.map(button => ({
...button,
titleWithShortcut: `${button.title}${getShortcut(button.method)}`,
iconClass: `icon-${button.icon}`,
@ -181,7 +178,7 @@ export default {
},
requestSync() {
if (this.isSyncPossible && !this.isSyncRequested) {
syncSvc.requestSync(true);
syncSvc.requestSync();
}
},
requestPublish() {
@ -189,16 +186,9 @@ export default {
publishSvc.requestPublish();
}
},
switchTheme() {
store.dispatch('data/switchThemeSetting');
},
pagedownClick(name) {
if (store.getters['content/isCurrentEditable']) {
const text = editorSvc.clEditor.getContent();
editorSvc.pagedownEditor.uiManager.doClick(name);
if (text !== editorSvc.clEditor.getContent()) {
badgeSvc.addBadge('formatButtons');
}
}
},
async editTitle(toggle) {
@ -208,13 +198,12 @@ export default {
} else {
const title = this.title.trim();
this.title = store.getters['file/current'].name;
if (title && this.title !== title) {
if (title) {
try {
await workspaceSvc.storeItem({
...store.getters['file/current'],
name: title,
});
badgeSvc.addBadge('editCurrentFileName');
} catch (e) {
// Cancel
}
@ -314,18 +303,6 @@ export default {
padding: 0 4px;
width: 38px;
&.navigation-bar__button--theme {
width: 34px;
padding: 0 7px;
opacity: 0.85;
&:active,
&:focus,
&:hover {
opacity: 1;
}
}
&.navigation-bar__button--stackedit {
opacity: 0.85;
@ -456,16 +433,6 @@ export default {
display: inline-block;
}
.navigation-bar__button--close {
color: lighten($link-color, 15%);
&:active,
&:focus,
&:hover {
color: lighten($link-color, 25%);
}
}
.navigation-bar__title--input {
cursor: pointer;

View File

@ -2,19 +2,12 @@
<div class="notification">
<div class="notification__item flex flex--row flex--align-center" v-for="(item, idx) in items" :key="idx">
<div class="notification__icon flex flex--column flex--center">
<icon-alert v-if="item.type === 'error'"></icon-alert>
<icon-check-circle v-else-if="item.type === 'badge'"></icon-check-circle>
<icon-information v-else></icon-information>
<icon-information v-if="item.type === 'info'"></icon-information>
<icon-alert v-else-if="item.type === 'error'"></icon-alert>
</div>
<div class="notification__content">
{{item.content}}
</div>
<button class="notification__button button" v-if="item.type === 'confirm'" @click="item.reject">
</button>
<button class="notification__button button" v-if="item.type === 'confirm'" @click="item.resolve">
</button>
</div>
</div>
</template>
@ -56,17 +49,4 @@ export default {
margin-right: 12px;
flex: none;
}
.notification__button {
color: $navbar-color;
padding: 8px;
flex: none;
&:active,
&:focus,
&:hover {
color: $navbar-hover-color;
background-color: $navbar-hover-background;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="preview">
<div class="preview__inner-1" @click="onClick" @scroll="onScroll">
<div class="preview__inner-2" :class="previewTheme" :style="{padding: styles.previewPadding}">
<div class="preview__inner-2" :style="{padding: styles.previewPadding}">
</div>
<div class="gutter" :style="{left: styles.previewGutterLeft + 'px'}">
<comment-list v-if="styles.previewGutterWidth"></comment-list>
@ -9,7 +9,7 @@
</div>
</div>
<div v-if="!styles.showEditor" class="preview__corner">
<button class="preview__button button" @click="toggleEditor(true)" v-title="'编辑文件'">
<button class="preview__button button" @click="toggleEditor(true)" v-title="'Edit file'">
<icon-pen></icon-pen>
</button>
</div>
@ -37,15 +37,9 @@ export default {
...mapGetters('file', [
'isCurrentTemp',
]),
...mapGetters('theme', [
'currPreviewTheme',
]),
...mapGetters('layout', [
'styles',
]),
previewTheme() {
return `preview-theme--${this.currPreviewTheme || 'default'}`;
},
},
methods: {
...mapActions('data', [

View File

@ -1,212 +0,0 @@
<template>
<div class="preview-in-page-buttons">
<ul>
<li class="before">
<icon-ellipsis></icon-ellipsis>
</li>
<li title="分享">
<a href="javascript:void(0)" @click="share"><icon-share></icon-share></a>
</li>
<li title="切换预览主题">
<dropdown-menu :selected="selectedTheme" :options="allThemes" :closeOnItemClick="false" @change="changeTheme">
<icon-select-theme></icon-select-theme>
</dropdown-menu>
</li>
<li title="Markdown语法帮助">
<a href="javascript:void(0)" @click="showHelp"><icon-help-circle></icon-help-circle></a>
</li>
</ul>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
// import juice from 'juice';
import store from '../store';
import DropdownMenu from './common/DropdownMenu';
import publishSvc from '../services/publishSvc';
import giteeGistProvider from '../services/providers/giteeGistProvider';
import gistProvider from '../services/providers/gistProvider';
export default {
components: {
DropdownMenu,
},
data: () => ({
allThemes: [{
name: '默认主题',
value: 'default',
}, {
name: '凝夜紫',
value: 'ningyezi',
}, {
name: '草原绿',
value: 'caoyuangreen',
}, {
name: '雁栖湖',
value: 'yanqihu',
}, {
name: '灵动蓝',
value: 'activeblue',
}, {
name: '极客黑',
value: 'jikebrack',
}, {
name: '极简黑',
value: 'simplebrack',
}, {
name: '全栈蓝',
value: 'allblue',
}, {
name: '自定义',
value: 'custom',
}],
baseCss: '',
sharing: false,
}),
computed: {
...mapGetters('theme', [
'currPreviewTheme',
'customPreviewThemeStyle',
]),
...mapGetters('publishLocation', {
publishLocations: 'current',
}),
selectedTheme() {
return {
value: this.currPreviewTheme || 'default',
};
},
},
methods: {
...mapActions('data', [
'toggleSideBar',
]),
async changeTheme(item) {
await store.dispatch('theme/setPreviewTheme', item.value);
//
if (item.value === 'custom' && !this.customPreviewThemeStyle) {
this.toggleSideBar(true);
store.dispatch('data/setSideBarPanel', 'previewTheme');
}
},
showHelp() {
this.toggleSideBar(true);
store.dispatch('data/setSideBarPanel', 'help');
},
async share() {
if (this.sharing) {
store.dispatch('notification/info', '分享链接创建中...请稍后再试');
return;
}
try {
const currentFile = store.getters['file/current'];
await store.dispatch('modal/open', { type: 'shareHtmlPre', name: currentFile.name });
this.sharing = true;
const mainToken = store.getters['workspace/mainWorkspaceToken'];
if (!mainToken) {
store.dispatch('notification/info', '登录主文档空间之后才可使用分享功能!');
return;
}
let tempGistId = null;
const isGithub = mainToken.providerId === 'githubAppData';
const gistProviderId = isGithub ? 'gist' : 'giteegist';
const filterLocations = this.publishLocations.filter(it => it.providerId === gistProviderId
&& it.url && it.gistId);
if (filterLocations.length > 0) {
tempGistId = filterLocations[0].gistId;
}
const location = (isGithub ? gistProvider : giteeGistProvider).makeLocation(
mainToken,
`分享-${currentFile.name}`,
true,
null,
);
location.templateId = 'styledHtmlWithTheme';
location.fileId = currentFile.id;
location.gistId = tempGistId;
const { gistId } = await publishSvc.publishLocationAndStore(location);
const sharePage = mainToken.providerId === 'githubAppData' ? 'gistshare.html' : 'share.html';
const url = `${window.location.protocol}//${window.location.host}/${sharePage}?id=${gistId}`;
await store.dispatch('modal/open', { type: 'shareHtml', name: currentFile.name, url });
} catch (err) {
if (err) {
store.dispatch('notification/error', err);
}
} finally {
this.sharing = false;
}
},
},
};
</script>
<style lang="scss">
@import '../styles/variables.scss';
.preview-in-page-buttons {
position: absolute;
bottom: 10px;
right: -98px;
height: 34px;
padding: 5px;
background-color: rgba(84, 96, 114, 0.4);
border-radius: $border-radius-base;
transition: 0.5s;
display: flex;
.dropdown-menu {
display: none;
}
&:active,
&:focus,
&:hover {
right: 0;
transition: 0.5s;
background-color: #546072;
.dropdown-menu {
display: block;
}
}
.dropdown-menu-items {
bottom: 100%;
top: unset;
}
ul {
padding: 0;
margin-left: 10px;
line-height: 20px;
li {
line-height: 16px;
width: 16px;
display: inline-block;
vertical-align: middle;
list-style: none;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
.icon {
color: #fff;
opacity: 0.7;
&:active,
&:focus,
&:hover {
opacity: 1;
}
}
}
.before {
margin-left: -16px;
margin-right: 0;
}
}
}
</style>

View File

@ -1,13 +1,13 @@
<template>
<div class="side-bar flex flex--column">
<div class="side-title flex flex--row">
<button v-if="panel !== 'menu'" class="side-title__button button" @click="setPanel('menu')" v-title="'主菜单'">
<icon-dots-horizontal></icon-dots-horizontal>
<button v-if="panel !== 'menu'" class="side-title__button button" @click="setPanel('menu')" v-title="'Main menu'">
<icon-arrow-left></icon-arrow-left>
</button>
<div class="side-title__title">
{{panelName}}
</div>
<button class="side-title__button button" @click="toggleSideBar(false)" v-title="'关闭侧边栏'">
<button class="side-title__button button" @click="toggleSideBar(false)" v-title="'Close side bar'">
<icon-close></icon-close>
</button>
</div>
@ -18,13 +18,11 @@
<publish-menu v-else-if="panel === 'publish'"></publish-menu>
<history-menu v-else-if="panel === 'history'"></history-menu>
<export-menu v-else-if="panel === 'export'"></export-menu>
<import-export-menu v-else-if="panel === 'importExport'"></import-export-menu>
<workspace-backup-menu v-else-if="panel === 'workspaceBackups'"></workspace-backup-menu>
<import-menu v-else-if="panel === 'import'"></import-menu>
<more-menu v-else-if="panel === 'more'"></more-menu>
<div v-else-if="panel === 'help'" class="side-bar__panel side-bar__panel--help">
<pre class="markdown-highlighting" v-html="markdownSample"></pre>
</div>
<edit-theme-menu v-else-if="panel === 'editTheme'"></edit-theme-menu>
<preview-theme-menu v-else-if="panel === 'previewTheme'"></preview-theme-menu>
<div class="side-bar__panel side-bar__panel--toc" :class="{'side-bar__panel--hidden': panel !== 'toc'}">
<toc>
</toc>
@ -41,26 +39,24 @@ import WorkspacesMenu from './menus/WorkspacesMenu';
import SyncMenu from './menus/SyncMenu';
import PublishMenu from './menus/PublishMenu';
import HistoryMenu from './menus/HistoryMenu';
import ImportExportMenu from './menus/ImportExportMenu';
import WorkspaceBackupMenu from './menus/WorkspaceBackupMenu';
import EditThemeMenu from './menus/EditThemeMenu';
import PreviewThemeMenu from './menus/PreviewThemeMenu';
import ExportMenu from './menus/ExportMenu';
import ImportMenu from './menus/ImportMenu';
import MoreMenu from './menus/MoreMenu';
import markdownSample from '../data/markdownSample.md';
import markdownConversionSvc from '../services/markdownConversionSvc';
import store from '../store';
const panelNames = {
menu: '菜单',
workspaces: '文档空间',
help: 'Markdown 帮助',
toc: '目录',
sync: '同步',
publish: '发布',
history: '文件历史',
importExport: '导入/导出',
workspaceBackups: '文档空间备份',
editTheme: '编辑区主题',
previewTheme: '预览区主题',
menu: 'Menu',
workspaces: 'Workspaces',
help: 'Markdown cheat sheet',
toc: 'Table of contents',
sync: 'Synchronize',
publish: 'Publish',
history: 'File history',
export: 'Export to disk',
import: 'Import from disk',
more: 'More',
};
export default {
@ -71,10 +67,9 @@ export default {
SyncMenu,
PublishMenu,
HistoryMenu,
ImportExportMenu,
WorkspaceBackupMenu,
EditThemeMenu,
PreviewThemeMenu,
ExportMenu,
ImportMenu,
MoreMenu,
},
data: () => ({
markdownSample: markdownConversionSvc.highlight(markdownSample),
@ -182,10 +177,8 @@ export default {
font-size: 0.95em;
p {
margin: 10px 15px;
font-size: 0.9rem;
opacity: 0.67;
line-height: 1.3;
margin: 10px;
line-height: 1.5;
}
}
</style>

View File

@ -8,7 +8,7 @@
<span v-for="stat in textStats" :key="stat.id">
<span class="stat-panel__value">{{stat.value}}</span> {{stat.name}}
</span>
<span class="stat-panel__value">{{line}} , {{column}} </span>
<span class="stat-panel__value">Ln {{line}}, Col {{column}}</span>
</div>
<div class="stat-panel__block stat-panel__block--right">
<span class="stat-panel__block-name">
@ -43,13 +43,14 @@ export default {
line: 0,
column: 0,
textStats: [
new Stat('字符', '[\\s\\S]'),
new Stat('字数', '\\S'),
new Stat('行数', '\n'),
new Stat('bytes', '[\\s\\S]'),
new Stat('words', '\\S+'),
new Stat('lines', '\n'),
],
htmlStats: [
new Stat('字数', '\\S'),
new Stat('段落', '\\S.*'),
new Stat('characters', '\\S'),
new Stat('words', '\\S+'),
new Stat('paragraphs', '\\S.*'),
],
}),
computed: mapGetters('layout', [

View File

@ -99,10 +99,6 @@ export default {
-ms-user-select: none;
user-select: none;
.app--dark & {
color: rgba(255, 255, 255, 0.67);
}
* {
font-weight: inherit;
pointer-events: none;
@ -154,9 +150,5 @@ export default {
height: 35px;
background-color: rgba(255, 255, 255, 0.2);
pointer-events: none;
.app--dark & {
color: rgba(0, 0, 0, 0.2);
}
}
</style>

View File

@ -1,56 +1,48 @@
<template>
<div class="tour" @keydown.esc.stop="skip">
<div class="tour" @keydown.esc="skip">
<div class="tour-step" :class="'tour-step--' + step" :style="stepStyle">
<div class="tour-step__inner" v-if="step === 'welcome'">
<h2>欢迎回来</h2>
<p>新的<b>StackEdit中文版</b>在这里</p>
<p>请单击<b>下一步</b>快速浏览</p>
<h2>Welcome back!</h2>
<p>The new <b>StackEdit 5</b> is here!</p>
<p>Please click <b>Next</b> to take a quick tour.</p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
<button class="button" @click="finish">Skip</button>
<button class="button button--resolve" @click="next">Next</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'editor'">
<h2>您的Markdown编辑器</h2>
<p>StackEdit中文版实时将Markdown转换为HTML</p>
<p>点击 <icon-side-preview></icon-side-preview> </p>
<h2>Your Markdown editor</h2>
<p>StackEdit converts your Markdown to HTML in real-time.</p>
<p>Click <icon-side-preview></icon-side-preview> to toggle the side preview.</p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
<button class="button" @click="finish">Skip</button>
<button class="button button--resolve" @click="next">Next</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'explorer'">
<h2>文件资源管理器</h2>
<p>StackEdit中文版可以管理文档空间中的多个文件和文件夹</p>
<p>点击 <icon-folder></icon-folder> </p>
<h2>File explorer</h2>
<p>StackEdit can manage multiple files and folders in a workspace.</p>
<p>Click <icon-folder></icon-folder> to open the file explorer.</p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
<button class="button" @click="finish">Skip</button>
<button class="button button--resolve" @click="next">Next</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'menu'">
<h2>切换侧边栏</h2>
<p>StackEdit中文版还可以同步和发布文件管理协作文档空间...</p>
<p>点击 <icon-provider provider-id="stackedit"></icon-provider> </p>
<h2>Do a lot more!</h2>
<p>StackEdit can also synchronize and publish your files, manage collaborative workspaces...</p>
<p>Click <icon-provider provider-id="stackedit"></icon-provider> to explore the menu.</p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'theme'">
<h2>切换主题</h2>
<p>StackEdit中文版可以切换亮/暗主题</p>
<p>点击 <icon-switch-theme></icon-switch-theme> </p>
<div class="tour-step__button-bar">
<button class="button" @click="finish">跳过</button>
<button class="button button--resolve" @click="next">下一步</button>
<button class="button" @click="finish">Skip</button>
<button class="button button--resolve" @click="next">Next</button>
</div>
</div>
<div class="tour-step__inner" v-else-if="step === 'end'">
<h2>Enjoy!</h2>
<p>如果您喜欢StackEdit中文版请在<a href="https://gitee.com/mafgwo/stackedit">Gitee仓库</a>上点一下Star谢谢</p>
<p>If you like StackEdit, please rate 5 stars on the <a target="_blank" href="https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg/reviews">Chrome Web Store</a>.</p>
<p>You can also star the project on <a target="_blank" href="https://github.com/benweet/stackedit">GitHub</a> and join the <a target="_blank" href="https://community.stackedit.io/">community</a>.</p>
<div class="tour-step__button-bar">
<button class="button button--resolve" @click="finish">确认</button>
<button class="button button--resolve" @click="finish">Ok</button>
</div>
</div>
</div>
@ -66,7 +58,6 @@ const steps = [
'editor',
'explorer',
'menu',
'theme',
'end',
];
@ -100,8 +91,7 @@ export default {
break;
}
case 'editor':
case 'menu':
case 'theme': {
case 'menu': {
style.left = `${anchorRect.left}px`;
break;
}
@ -149,8 +139,7 @@ export default {
position: absolute;
}
$tour-step-background: transparentize(mix(#f3f3f3, $selection-highlighting-color, 75%), 0.025);
$tour-step-darkbackground: transparentize(mix(#4d4d4d, $selection-highlighting-color, 75%), 0.025);
$tour-step-background: mix(#f3f3f3, $selection-highlighting-color, 75%);
$tour-step-width: 240px;
.tour-step__inner {
@ -163,10 +152,6 @@ $tour-step-width: 240px;
text-align: center;
border-radius: $border-radius-base;
.app--dark & {
background-color: $tour-step-darkbackground;
}
h2 {
margin: 0;
@ -199,16 +184,11 @@ $tour-step-width: 240px;
right: 0;
border-top: 10px solid $tour-step-background;
border-left: 10px solid transparent;
.app--dark & {
border-top: 10px solid $tour-step-darkbackground;
}
}
}
.tour-step--editor &,
.tour-step--menu &,
.tour-step--theme & {
.tour-step--menu & {
right: 15px;
border-top-right-radius: 0;
@ -217,10 +197,6 @@ $tour-step-width: 240px;
right: -10px;
border-top: 10px solid $tour-step-background;
border-right: 10px solid transparent;
.app--dark & {
border-top: 10px solid $tour-step-darkbackground;
}
}
}
@ -233,10 +209,6 @@ $tour-step-width: 240px;
left: -10px;
border-top: 10px solid $tour-step-background;
border-left: 10px solid transparent;
.app--dark & {
border-top: 10px solid $tour-step-darkbackground;
}
}
}
}

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