支持分享功能

This commit is contained in:
xiaoqi.cxq 2023-03-30 15:56:24 +08:00
parent 58c9144612
commit ae828cfb56
22 changed files with 562 additions and 57 deletions

View File

@ -71,6 +71,7 @@ StackEdit中文版
- 支持预览区域选择主题样式2022-12-04 - 支持预览区域选择主题样式2022-12-04
- Gitlab的支持优化2023-02-23 - Gitlab的支持优化2023-02-23
- 导出HTML、PDF支持带预览主题导出2023-02-26 - 导出HTML、PDF支持带预览主题导出2023-02-26
- 支持分享文档2023-03-30
## 国外开源版本弊端: ## 国外开源版本弊端:
- 作者已经不维护了 - 作者已经不维护了

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "stackedit", "name": "stackedit",
"version": "5.15.17", "version": "5.15.19",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "stackedit", "name": "stackedit",
"version": "5.15.18", "version": "5.15.19",
"description": "免费, 开源, 功能齐全的 Markdown 编辑器", "description": "免费, 开源, 功能齐全的 Markdown 编辑器",
"author": "Benoit Schweblin, 豆萁", "author": "Benoit Schweblin, 豆萁",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -61,13 +61,15 @@ module.exports = (app) => {
res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`)); res.redirect(`./app#providerId=googleDrive&state=${encodeURIComponent(req.query.state)}`));
// Serve the static folder with 30 day max-age // Serve the static folder with 30 day max-age
app.use('/themes', serveStatic(resolvePath('static/themes'), { app.use('/themes', serveStatic(resolvePath('static/themes'), {
maxAge: '1d', maxAge: '5d',
})); }));
// Serve style.css with 1 day max-age // Serve style.css with 1 day max-age
app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), { app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), {
maxAge: '1d', maxAge: '1d',
})); }));
// Serve share.html
app.get('/share.html', (req, res) => res.sendFile(resolvePath('static/landing/share.html')));
// Serve static resources // Serve static resources
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {

View File

@ -64,6 +64,8 @@ import GiteeOpenModal from './modals/providers/GiteeOpenModal';
import GiteeSaveModal from './modals/providers/GiteeSaveModal'; import GiteeSaveModal from './modals/providers/GiteeSaveModal';
import GiteeWorkspaceModal from './modals/providers/GiteeWorkspaceModal'; import GiteeWorkspaceModal from './modals/providers/GiteeWorkspaceModal';
import GiteePublishModal from './modals/providers/GiteePublishModal'; 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 GitlabAccountModal from './modals/providers/GitlabAccountModal';
import GitlabOpenModal from './modals/providers/GitlabOpenModal'; import GitlabOpenModal from './modals/providers/GitlabOpenModal';
import GitlabPublishModal from './modals/providers/GitlabPublishModal'; import GitlabPublishModal from './modals/providers/GitlabPublishModal';
@ -131,6 +133,8 @@ export default {
GiteeSaveModal, GiteeSaveModal,
GiteeWorkspaceModal, GiteeWorkspaceModal,
GiteePublishModal, GiteePublishModal,
GiteeGistSyncModal,
GiteeGistPublishModal,
GitlabAccountModal, GitlabAccountModal,
GitlabOpenModal, GitlabOpenModal,
GitlabPublishModal, GitlabPublishModal,

View File

@ -4,6 +4,9 @@
<li class="before"> <li class="before">
<icon-ellipsis></icon-ellipsis> <icon-ellipsis></icon-ellipsis>
</li> </li>
<li title="分享">
<a href="javascript:void(0)" @click="share"><icon-share></icon-share></a>
</li>
<li title="切换预览主题"> <li title="切换预览主题">
<dropdown-menu :selected="selectedTheme" :options="allThemes" :closeOnItemClick="false" @change="changeTheme"> <dropdown-menu :selected="selectedTheme" :options="allThemes" :closeOnItemClick="false" @change="changeTheme">
<icon-select-theme></icon-select-theme> <icon-select-theme></icon-select-theme>
@ -21,6 +24,8 @@ import { mapGetters, mapActions } from 'vuex';
// import juice from 'juice'; // import juice from 'juice';
import store from '../store'; import store from '../store';
import DropdownMenu from './common/DropdownMenu'; import DropdownMenu from './common/DropdownMenu';
import publishSvc from '../services/publishSvc';
import giteeGistProvider from '../services/providers/giteeGistProvider';
export default { export default {
components: { components: {
@ -56,12 +61,16 @@ export default {
value: 'custom', value: 'custom',
}], }],
baseCss: '', baseCss: '',
sharing: false,
}), }),
computed: { computed: {
...mapGetters('theme', [ ...mapGetters('theme', [
'currPreviewTheme', 'currPreviewTheme',
'customPreviewThemeStyle', 'customPreviewThemeStyle',
]), ]),
...mapGetters('publishLocation', {
publishLocations: 'current',
}),
selectedTheme() { selectedTheme() {
return { return {
value: this.currPreviewTheme || 'default', value: this.currPreviewTheme || 'default',
@ -84,6 +93,43 @@ export default {
this.toggleSideBar(true); this.toggleSideBar(true);
store.dispatch('data/setSideBarPanel', 'help'); 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 giteeGistId = null;
const filterLocations = this.publishLocations.filter(it => it.providerId === 'giteegist' && it.url && it.gistId);
if (filterLocations.length > 0) {
giteeGistId = filterLocations[0].gistId;
}
const location = giteeGistProvider.makeLocation(
mainToken,
`分享-${currentFile.name}`,
true,
null,
);
location.templateId = 'styledHtmlWithTheme';
location.fileId = currentFile.id;
location.gistId = giteeGistId;
const { gistId } = await publishSvc.publishLocationAndStore(location);
const url = `${window.location.protocol}//${window.location.host}/share.html?id=${gistId}`;
await store.dispatch('modal/open', { type: 'shareHtml', name: currentFile.name, url });
} catch (err) {
/* cancel */
} finally {
this.sharing = false;
}
},
}, },
}; };
</script> </script>
@ -94,7 +140,7 @@ export default {
.preview-in-page-buttons { .preview-in-page-buttons {
position: absolute; position: absolute;
bottom: 10px; bottom: 10px;
right: -68px; right: -98px;
height: 34px; height: 34px;
padding: 5px; padding: 5px;
background-color: rgba(84, 96, 114, 0.4); background-color: rgba(84, 96, 114, 0.4);

View File

@ -43,7 +43,7 @@
<div v-for="token in githubTokens" :key="token.sub"> <div v-for="token in githubTokens" :key="token.sub">
<menu-entry @click.native="publishGist(token)"> <menu-entry @click.native="publishGist(token)">
<icon-provider slot="icon" provider-id="gist"></icon-provider> <icon-provider slot="icon" provider-id="gist"></icon-provider>
<div>发布到 Gist</div> <div>发布到 GitHubGist</div>
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </menu-entry>
<menu-entry @click.native="publishGithub(token)"> <menu-entry @click.native="publishGithub(token)">
@ -53,6 +53,11 @@
</menu-entry> </menu-entry>
</div> </div>
<div v-for="token in giteeTokens" :key="token.sub"> <div v-for="token in giteeTokens" :key="token.sub">
<menu-entry @click.native="publishGiteeGist(token)">
<icon-provider slot="icon" provider-id="giteegist"></icon-provider>
<div>发布到 GiteeGist</div>
<span>{{token.name}}</span>
</menu-entry>
<menu-entry @click.native="publishGitee(token)"> <menu-entry @click.native="publishGitee(token)">
<icon-provider slot="icon" provider-id="gitee"></icon-provider> <icon-provider slot="icon" provider-id="gitee"></icon-provider>
<div>发布到 Gitee</div> <div>发布到 Gitee</div>
@ -289,8 +294,9 @@ export default {
publishBloggerPage: publishModalOpener('bloggerPagePublish', 'publishToBloggerPage'), publishBloggerPage: publishModalOpener('bloggerPagePublish', 'publishToBloggerPage'),
publishDropbox: publishModalOpener('dropboxPublish', 'publishToDropbox'), publishDropbox: publishModalOpener('dropboxPublish', 'publishToDropbox'),
publishGithub: publishModalOpener('githubPublish', 'publishToGithub'), publishGithub: publishModalOpener('githubPublish', 'publishToGithub'),
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
publishGist: publishModalOpener('gistPublish', 'publishToGist'), publishGist: publishModalOpener('gistPublish', 'publishToGist'),
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
publishGiteeGist: publishModalOpener('giteeGistPublish', 'publishGiteeGist'),
publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'), publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'),
publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'), publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'),
publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'), publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'),

View File

@ -46,7 +46,7 @@
</menu-entry> </menu-entry>
<menu-entry @click.native="saveGist(token)"> <menu-entry @click.native="saveGist(token)">
<icon-provider slot="icon" provider-id="gist"></icon-provider> <icon-provider slot="icon" provider-id="gist"></icon-provider>
<div>在Gist上保存</div> <div>在GitHubGist上保存</div>
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </menu-entry>
</div> </div>
@ -61,6 +61,11 @@
<div>在Gitee上保存</div> <div>在Gitee上保存</div>
<span>{{token.name}}</span> <span>{{token.name}}</span>
</menu-entry> </menu-entry>
<menu-entry @click.native="saveGiteeGist(token)">
<icon-provider slot="icon" provider-id="giteegist"></icon-provider>
<div>在GiteeGist上保存</div>
<span>{{token.name}}</span>
</menu-entry>
</div> </div>
<div v-for="token in gitlabTokens" :key="token.sub"> <div v-for="token in gitlabTokens" :key="token.sub">
<menu-entry @click.native="openGitlab(token)"> <menu-entry @click.native="openGitlab(token)">
@ -330,6 +335,12 @@ export default {
badgeSvc.addBadge('saveOnGist'); badgeSvc.addBadge('saveOnGist');
} catch (e) { /* cancel */ } } catch (e) { /* cancel */ }
}, },
async saveGiteeGist(token) {
try {
await openSyncModal(token, 'giteeGistSync');
badgeSvc.addBadge('saveOnGiteeGist');
} catch (e) { /* cancel */ }
},
async openGitlab(token) { async openGitlab(token) {
try { try {
const syncLocation = await store.dispatch('modal/open', { const syncLocation = await store.dispatch('modal/open', {

View File

@ -4,8 +4,8 @@
<div class="modal__image"> <div class="modal__image">
<icon-upload></icon-upload> <icon-upload></icon-upload>
</div> </div>
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> is published to the following location(s):</p> <p v-if="publishLocations.length"><b>{{currentFileName}}</b> 被发布到了以下位置:</p>
<p v-else><b>{{currentFileName}}</b> is not published yet.</p> <p v-else><b>{{currentFileName}}</b> 还没有被发布.</p>
<div> <div>
<div class="publish-entry flex flex--column" v-for="location in publishLocations" :key="location.id"> <div class="publish-entry flex flex--column" v-for="location in publishLocations" :key="location.id">
<div class="publish-entry__header flex flex--row flex--align-center"> <div class="publish-entry__header flex flex--row flex--align-center">
@ -26,7 +26,7 @@
{{location.url}} {{location.url}}
</div> </div>
<div class="publish-entry__buttons flex flex--row flex--center" v-if="location.url"> <div class="publish-entry__buttons flex flex--row flex--center" v-if="location.url">
<button class="publish-entry__button button" v-clipboard="location.url" @click="info('Location URL copied to clipboard!')" v-title="'复制URL'"> <button class="publish-entry__button button" v-clipboard="location.url" @click="info('位置URL已复制到剪贴板!')" v-title="'复制URL'">
<icon-content-copy></icon-content-copy> <icon-content-copy></icon-content-copy>
</button> </button>
<a class="publish-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'打开位置'"> <a class="publish-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'打开位置'">
@ -34,6 +34,19 @@
</a> </a>
</div> </div>
</div> </div>
<div class="publish-entry__row flex flex--row flex--align-center" v-if="shareUrl(location)">
<div class="publish-entry__url">
分享链接: {{shareUrl(location)}}
</div>
<div class="publish-entry__buttons flex flex--row flex--center">
<button class="publish-entry__button button" v-clipboard="shareUrl(location)" @click="info('分享URL已复制到剪贴板!')" v-title="'复制分享URL'">
<icon-content-copy></icon-content-copy>
</button>
<a class="publish-entry__button button" :href="shareUrl(location)" target="_blank" v-title="'打开分享'">
<icon-open-in-new></icon-open-in-new>
</a>
</div>
</div>
</div> </div>
</div> </div>
<div class="modal__info" v-if="publishLocations.length"> <div class="modal__info" v-if="publishLocations.length">
@ -75,6 +88,16 @@ export default {
store.commit('publishLocation/deleteItem', location.id); store.commit('publishLocation/deleteItem', location.id);
badgeSvc.addBadge('removePublishLocation'); badgeSvc.addBadge('removePublishLocation');
}, },
shareUrl(location) {
if (location.providerId !== 'giteegist') {
return null;
}
if (!location.url) {
return null;
}
const splitIndex = location.url.lastIndexOf('/');
return `${window.location.protocol}//${window.location.host}/share.html?id=${location.url.substr(splitIndex + 1)}`;
},
}, },
}; };
</script> </script>

View File

@ -1,11 +1,11 @@
<template> <template>
<modal-inner aria-label="发布到Gist"> <modal-inner aria-label="发布到GitHubGist">
<div class="modal__content"> <div class="modal__content">
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gist"></icon-provider> <icon-provider provider-id="gist"></icon-provider>
</div> </div>
<p>发布<b> {{CurrentFileName}} </b><b>Gist</b></p> <p>发布<b> {{CurrentFileName}} </b><b>GitHubGist</b></p>
<form-entry label="Filename" error="filename"> <form-entry label="文件名" error="filename">
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
</form-entry> </form-entry>
<div class="form-entry"> <div class="form-entry">
@ -15,10 +15,10 @@
</label> </label>
</div> </div>
</div> </div>
<form-entry label="Existing Gist ID" info="可选的"> <form-entry label="存在Gist ID" info="可选的">
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">
如果文件存在于Gist中则将被覆盖 如果文件存在于GitHubGist中则将被覆盖
</div> </div>
</form-entry> </form-entry>
<form-entry label="Template"> <form-entry label="Template">

View File

@ -1,11 +1,11 @@
<template> <template>
<modal-inner aria-label=" Gist 同步"> <modal-inner aria-label=" GitHubGist 同步">
<div class="modal__content"> <div class="modal__content">
<div class="modal__image"> <div class="modal__image">
<icon-provider provider-id="gist"></icon-provider> <icon-provider provider-id="gist"></icon-provider>
</div> </div>
<p><b> {{currentFileName}} </b>保存到<b>Gist</b>并保持同步</p> <p><b> {{currentFileName}} </b>保存到<b>GitHubGist</b>并保持同步</p>
<form-entry label="Filename" error="filename"> <form-entry label="文件名" error="filename">
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
</form-entry> </form-entry>
<div class="form-entry"> <div class="form-entry">
@ -15,10 +15,10 @@
</label> </label>
</div> </div>
</div> </div>
<form-entry label="Existing Gist ID" info="可选的"> <form-entry label="存在Gist ID" info="可选的">
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()"> <input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
<div class="form-entry__info"> <div class="form-entry__info">
如果文件存在于Gist中则将被覆盖 如果文件存在于GitHubGist中则将被覆盖
</div> </div>
</form-entry> </form-entry>
</div> </div>

View File

@ -0,0 +1,79 @@
<template>
<modal-inner aria-label="发布到GiteeGist">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="giteegist"></icon-provider>
</div>
<p>发布<b> {{CurrentFileName}} </b><b>GiteeGist</b></p>
<form-entry label="文件名" error="filename">
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
</form-entry>
<div class="form-entry">
<div class="form-entry__checkbox">
<label>
<input type="checkbox" v-model="isPublic"> 公开的
</label>
</div>
</div>
<form-entry label="存在Gist ID" info="可选的">
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
<div class="form-entry__info">
如果文件存在于GiteeGist中则将被覆盖
</div>
</form-entry>
<form-entry label="Template">
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
{{ template.name }}
</option>
</select>
<div class="form-entry__actions">
<a href="javascript:void(0)" @click="configureTemplates">配置模板</a>
</div>
</form-entry>
<div class="modal__info">
<b>ProTip:</b> You can provide a value for <code>title</code> in the <a href="javascript:void(0)" @click="openFileProperties">file properties</a>.
</div>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">取消</button>
<button class="button button--resolve" @click="resolve()">确认</button>
</div>
</modal-inner>
</template>
<script>
import giteeGistProvider from '../../../services/providers/giteeGistProvider';
import modalTemplate from '../common/modalTemplate';
export default modalTemplate({
data: () => ({
filename: '',
gistId: '',
}),
computedLocalSettings: {
isPublic: 'gistIsPublic',
selectedTemplate: 'gistPublishTemplate',
},
created() {
this.filename = `${this.currentFileName}.md`;
},
methods: {
resolve() {
if (!this.filename) {
this.setError('filename');
} else {
// Return new location
const location = giteeGistProvider.makeLocation(
this.config.token,
this.filename,
this.isPublic,
this.gistId,
);
location.templateId = this.selectedTemplate;
this.config.resolve(location);
}
},
},
});
</script>

View File

@ -0,0 +1,64 @@
<template>
<modal-inner aria-label=" GiteeGist 同步">
<div class="modal__content">
<div class="modal__image">
<icon-provider provider-id="giteegist"></icon-provider>
</div>
<p><b> {{currentFileName}} </b>保存到<b>GiteeGist</b>并保持同步</p>
<form-entry label="文件名" error="filename">
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
</form-entry>
<div class="form-entry">
<div class="form-entry__checkbox">
<label>
<input type="checkbox" v-model="isPublic"> 公开的
</label>
</div>
</div>
<form-entry label="存在Gist ID" info="可选的">
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
<div class="form-entry__info">
如果文件存在于GiteeGist中则将被覆盖
</div>
</form-entry>
</div>
<div class="modal__button-bar">
<button class="button" @click="config.reject()">取消</button>
<button class="button button--resolve" @click="resolve()">确认</button>
</div>
</modal-inner>
</template>
<script>
import giteeGistProvider from '../../../services/providers/giteeGistProvider';
import modalTemplate from '../common/modalTemplate';
export default modalTemplate({
data: () => ({
filename: '',
gistId: '',
}),
computedLocalSettings: {
isPublic: 'gistIsPublic',
},
created() {
this.filename = `${this.currentFileName}.md`;
},
methods: {
resolve() {
if (!this.filename) {
this.setError('filename');
} else {
// Return new location
const location = giteeGistProvider.makeLocation(
this.config.token,
this.filename,
this.isPublic,
this.gistId,
);
this.config.resolve(location);
}
},
},
});
</script>

View File

@ -316,6 +316,11 @@ export default [
'GitHub保存', 'GitHub保存',
'使用“同步”菜单将文件保存在GitHub仓库中。', '使用“同步”菜单将文件保存在GitHub仓库中。',
), ),
new Feature(
'saveOnGist',
'GitHubGist保存',
'使用“同步”菜单将文件保存在GitHubGist中。',
),
new Feature( new Feature(
'openFromGitee', 'openFromGitee',
'Gitee阅读器', 'Gitee阅读器',
@ -327,9 +332,9 @@ export default [
'使用“同步”菜单将文件保存在Gitee仓库中。', '使用“同步”菜单将文件保存在Gitee仓库中。',
), ),
new Feature( new Feature(
'saveOnGist', 'saveOnGiteeGist',
'Gist保存', 'GiteeGist保存',
'使用“同步”菜单将文件保存在GIST中。', '使用“同步”菜单将文件保存在GiteeGist中。',
), ),
new Feature( new Feature(
'openFromGitlab', 'openFromGitlab',
@ -405,14 +410,19 @@ export default [
), ),
new Feature( new Feature(
'publishToGist', 'publishToGist',
'Gist发布', 'GitHubGist发布',
'使用“发布”菜单将文件发布到GIST。', '使用“发布”菜单将文件发布到GitHubGist。',
), ),
new Feature( new Feature(
'publishToGitee', 'publishToGitee',
'Gitee发布', 'Gitee发布',
'使用“发布”菜单将文件发布到Gitee仓库。', '使用“发布”菜单将文件发布到Gitee仓库。',
), ),
new Feature(
'publishToGiteeGist',
'GiteeGist发布',
'使用“发布”菜单将文件发布到GiteeGist。',
),
new Feature( new Feature(
'publishToGitlab', 'publishToGitlab',
'GitLab发布', 'GitLab发布',

View File

@ -60,6 +60,15 @@ export default {
'取消', '取消',
'确认清理', '确认清理',
), ),
shareHtml: simpleModal(
config => `<p>给文档 "${config.name}" 创建了分享链接如下:<br/><a href="${config.url}" target="_blank">${config.url}</a><br/>关闭该窗口后可以到发布中查看分享链接。</p>`,
'关闭窗口',
),
shareHtmlPre: simpleModal(
config => `<p>将给文档 "${config.name}" 创建分享链接创建后将会把文档公开发布到GiteeGist中。您确定吗</p>`,
'取消',
'确认分享',
),
signInForComment: simpleModal( signInForComment: simpleModal(
`<p>您必须使用 Google 登录才能开始评论。</p> `<p>您必须使用 Google 登录才能开始评论。</p>
<div class="modal__info"><b>注意:</b> </div>`, <div class="modal__info"><b>注意:</b> </div>`,

View File

@ -29,6 +29,7 @@ export default {
return 'couchdb'; return 'couchdb';
case 'giteeAppData': case 'giteeAppData':
case 'giteeWorkspace': case 'giteeWorkspace':
case 'giteegist':
return 'gitee'; return 'gitee';
default: default:
return this.providerId; return this.providerId;

3
src/icons/Share.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
<svg t="1680140298859" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2766" width="32" height="32"><path d="M769.14815 670.390403c-44.430932 0-84.182284 19.999496-110.768803 51.471278L389.219117 565.736878c6.597255-16.571421 10.228969-34.653241 10.228969-53.594639 0-17.006326-2.940982-33.332153-8.320503-48.497551l270.88143-157.119457c26.511817 29.069059 64.702628 47.312562 107.138112 47.312562 80.055291 0 144.95337-64.899102 144.95337-144.95337 0-80.055291-64.898079-144.954393-144.95337-144.954393s-144.95337 64.899102-144.95337 144.954393c0 15.991206 2.600221 31.386848 7.382131 45.776579L359.655801 412.377048c-26.417673-27.833929-63.756069-45.181015-105.161085-45.181015-80.055291 0-144.954393 64.890916-144.954393 144.946206 0 80.055291 64.898079 144.967696 144.954393 144.967696 39.409568 0 75.128071-15.741519 101.256148-41.24845l274.172383 159.024853c-3.725858 12.8384-5.729491 26.409486-5.729491 40.457434 0 80.0645 64.898079 144.954393 144.95337 144.954393s144.95337-64.889893 144.95337-144.954393C914.101519 735.297692 849.20344 670.390403 769.14815 670.390403z" p-id="2767"></path></svg>
</template>

View File

@ -64,6 +64,7 @@ import FindReplace from './FindReplace';
import SelectTheme from './SelectTheme'; import SelectTheme from './SelectTheme';
import Copy from './Copy'; import Copy from './Copy';
import Ellipsis from './Ellipsis'; import Ellipsis from './Ellipsis';
import Share from './Share';
Vue.component('iconProvider', Provider); Vue.component('iconProvider', Provider);
Vue.component('iconFormatBold', FormatBold); Vue.component('iconFormatBold', FormatBold);
@ -130,3 +131,4 @@ Vue.component('iconFindReplace', FindReplace);
Vue.component('iconSelectTheme', SelectTheme); Vue.component('iconSelectTheme', SelectTheme);
Vue.component('iconCopy', Copy); Vue.component('iconCopy', Copy);
Vue.component('iconEllipsis', Ellipsis); Vue.component('iconEllipsis', Ellipsis);
Vue.component('iconShare', Share);

View File

@ -0,0 +1,95 @@
import store from '../../store';
import giteeHelper from './helpers/giteeHelper';
import Provider from './common/Provider';
import utils from '../utils';
import userSvc from '../userSvc';
export default new Provider({
id: 'giteegist',
name: 'GiteeGist',
getToken({ sub }) {
return store.getters['data/giteeTokensBySub'][sub];
},
getLocationUrl({ gistId }) {
return `https://gitee.com/mafgwo/codes/${gistId}`;
},
getLocationDescription({ filename }) {
return filename;
},
async downloadContent(token, syncLocation) {
const content = await giteeHelper.downloadGist({
...syncLocation,
token,
});
return Provider.parseContent(content, `${syncLocation.fileId}/content`);
},
async uploadContent(token, content, syncLocation) {
const file = store.state.file.itemsById[syncLocation.fileId];
const description = utils.sanitizeName(file && file.name);
const gist = await giteeHelper.uploadGist({
...syncLocation,
token,
description,
content: Provider.serializeContent(content),
});
return {
...syncLocation,
gistId: gist.id,
};
},
async publish(token, html, metadata, publishLocation) {
const gist = await giteeHelper.uploadGist({
...publishLocation,
token,
description: metadata.title,
content: html,
});
return {
...publishLocation,
gistId: gist.id,
};
},
makeLocation(token, filename, isPublic, gistId) {
return {
providerId: this.id,
sub: token.sub,
filename,
isPublic,
gistId,
};
},
async listFileRevisions({ token, syncLocation }) {
const entries = await giteeHelper.getGistCommits({
...syncLocation,
token,
});
return entries.map((entry) => {
const sub = `${giteeHelper.subPrefix}:${entry.user.id}`;
userSvc.addUserInfo({ id: sub, name: entry.user.login, imageUrl: entry.user.avatar_url });
return {
sub,
id: entry.version,
message: entry.commit && entry.commit.message,
created: new Date(entry.committed_at).getTime(),
};
});
},
async loadFileRevision() {
// Revision are already loaded
return false;
},
// async getFileRevisionContent({
// token,
// contentId,
// syncLocation,
// revisionId,
// }) {
// const data = await giteeHelper.downloadGistRevision({
// ...syncLocation,
// token,
// sha: revisionId,
// });
// return Provider.parseContent(data, contentId);
// },
});

View File

@ -346,8 +346,8 @@ export default {
}, },
/** /**
* https://developer.gitee.com/v3/gists/#create-a-gist * https://gitee.com/api/v5/swagger#/postV5Gists
* https://developer.gitee.com/v3/gists/#edit-a-gist * https://gitee.com/api/v5/swagger#/patchV5GistsId
*/ */
async uploadGist({ async uploadGist({
token, token,
@ -357,8 +357,7 @@ export default {
isPublic, isPublic,
gistId, gistId,
}) { }) {
const refreshedToken = await this.refreshToken(token); const { body } = await request(token, gistId ? {
const { body } = await request(refreshedToken, gistId ? {
method: 'PATCH', method: 'PATCH',
url: `https://gitee.com/api/v5/gists/${gistId}`, url: `https://gitee.com/api/v5/gists/${gistId}`,
body: { body: {
@ -386,16 +385,15 @@ export default {
}, },
/** /**
* https://developer.gitee.com/v3/gists/#get-a-single-gist * https://gitee.com/api/v5/swagger#/getV5Gists
*/ */
async downloadGist({ async downloadGist({
token, token,
gistId, gistId,
filename, filename,
}) { }) {
const refreshedToken = await this.refreshToken(token); const result = (await request(token, {
const result = (await request(refreshedToken, { url: `https://api.github.com/gists/${gistId}`,
url: `https://gitee.com/api/v5/gists/${gistId}`,
})).body.files[filename]; })).body.files[filename];
if (!result) { if (!result) {
throw new Error('Gist file not found.'); throw new Error('Gist file not found.');
@ -404,35 +402,15 @@ export default {
}, },
/** /**
* https://developer.gitee.com/v3/gists/#list-gist-commits * https://gitee.com/api/v5/swagger#/getV5GistsIdCommits
*/ */
async getGistCommits({ async getGistCommits({
token, token,
gistId, gistId,
}) { }) {
const refreshedToken = await this.refreshToken(token); const { body } = await request(token, {
const { body } = await request(refreshedToken, {
url: `https://gitee.com/api/v5/gists/${gistId}/commits`, url: `https://gitee.com/api/v5/gists/${gistId}/commits`,
}); });
return body; return body;
}, },
/**
* https://developer.gitee.com/v3/gists/#get-a-specific-revision-of-a-gist
*/
async downloadGistRevision({
token,
gistId,
filename,
sha,
}) {
const refreshedToken = await this.refreshToken(token);
const result = (await request(refreshedToken, {
url: `https://gitee.com/api/v5/gists/${gistId}/${sha}`,
})).body.files[filename];
if (!result) {
throw new Error('Gist file not found.');
}
return result.content;
},
}; };

View File

@ -139,6 +139,12 @@ const requestPublish = () => {
}); });
}; };
const publishLocationAndStore = async (publishLocation, commitMsg) => {
const publishLocationToStore = await publish(publishLocation, commitMsg);
workspaceSvc.addPublishLocation(publishLocationToStore);
return publishLocationToStore;
};
const createPublishLocation = (publishLocation, featureId) => { const createPublishLocation = (publishLocation, featureId) => {
const currentFile = store.getters['file/current']; const currentFile = store.getters['file/current'];
publishLocation.fileId = currentFile.id; publishLocation.fileId = currentFile.id;
@ -157,8 +163,7 @@ const createPublishLocation = (publishLocation, featureId) => {
return; return;
} }
} }
const publishLocationToStore = await publish(publishLocation, commitMsg); await publishLocationAndStore(publishLocation, commitMsg);
workspaceSvc.addPublishLocation(publishLocationToStore);
store.dispatch('notification/info', `添加了一个新的发布位置 "${currentFile.name}".`); store.dispatch('notification/info', `添加了一个新的发布位置 "${currentFile.name}".`);
if (featureId) { if (featureId) {
badgeSvc.addBadge(featureId); badgeSvc.addBadge(featureId);
@ -169,5 +174,6 @@ const createPublishLocation = (publishLocation, featureId) => {
export default { export default {
requestPublish, requestPublish,
publishLocationAndStore,
createPublishLocation, createPublishLocation,
}; };

165
static/landing/share.html Normal file
View File

@ -0,0 +1,165 @@
<!DOCTYPE html>
<html>
<head>
<title>StackEdit中文版</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="https://stackedit.cn/">
<link rel="icon" href="static/landing/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="static/landing/favicon.ico" type="image/x-icon">
<meta charset="UTF-8">
<meta name="keywords" content="Markdown编辑器,StackEdit中文版,StackEdit汉化版,StackEdit,在线Markdown,笔记利器,Markdown笔记">
<meta name="description"
content="支持直接将码云Gitee、GitHub、Gitea等仓库作为笔记存储仓库且支持拖拽/粘贴上传图片并且可以直接在页面编辑同步和管理的Markdown编辑器。">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<meta name="baidu-site-verification" content="code-tGpn2BT069" />
<meta name="msvalidate.01" content="90A9558158543277BD284CFA054E7F5B" />
<link rel="stylesheet" href="style.css">
<style>
.share-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #383c4a;
color: #fff;
padding: 10px;
box-sizing: border-box;
z-index: 99999;
}
.share-header .logo {
margin: 0 0 -8px 0;
}
.share-header nav {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.share-header nav ul {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
}
.share-header nav li {
margin: 0 10px;
}
.share-header nav a {
color: #fff;
text-decoration: none;
}
.share-header nav a:hover {
text-decoration: underline;
}
.share-content {
margin-top: 60px;
}
</style>
<script type="text/javascript">
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
function appendTagHtml(newdoc, tagName, targetParentEle) {
const tags = newdoc.getElementsByTagName(tagName);
if (!tags) {
return;
}
for (let i = 0; i < tags.length; i++) {
targetParentEle.append(tags[i]);
}
}
window.onload = function() {
const xhr = new XMLHttpRequest();
const gistId = getQueryString('id');
const workspaces = window.localStorage.getItem('data/workspaces');
let accessToken = null;
if (workspaces) {
const workspacesObj = JSON.parse(workspaces);
const sub = workspacesObj.data && workspacesObj.data.main && workspacesObj.data.main.sub;
if (sub) {
const tokens = window.localStorage.getItem('data/tokens');
if (tokens) {
const tokensObj = JSON.parse(tokens);
accessToken = tokensObj.data && tokensObj.data.gitee && tokensObj.data.gitee[sub] && tokensObj.data.gitee[sub].accessToken;
}
}
}
let url = `https://gitee.com/api/v5/gists/${gistId}`;
if (accessToken) {
url = `${url}?access_token=${accessToken}`;
}
xhr.open('GET', url);
xhr.onload = function() {
if (xhr.status === 200) {
const newdoc = document.implementation.createHTMLDocument("");
const body = JSON.parse(xhr.responseText);
for (let key in body.files) {
newdoc.documentElement.innerHTML = body.files[key].content;
}
const currHead = document.head;
// 头部
appendTagHtml(newdoc, 'style', currHead);
// title
document.title = newdoc.title + ' - StackEdit中文版';
// 内容
const shareContent = document.getElementsByClassName('share-content')[0];
shareContent.innerHTML = newdoc.body.innerHTML;
} else if (xhr.status === 403) {
const rateLimit = xhr.responseText && xhr.responseText.indexOf('Rate Limit') >= 0;
const appUri = `${window.location.protocol}//${window.location.host}/app`;
document.getElementsByClassName('share-content')[0].innerHTML = `${rateLimit ? "请求太过频繁" : "无权限访问"},请登录 <a href="${appUri}" target="_brank">主文档空间</a> 后再刷新此页面!`;
} else {
console.error('An error occurred: ' + xhr.status);
document.getElementsByClassName('share-content')[0].innerHTML = '分享内容获取失败或已失效!';
}
};
xhr.send();
}
</script>
</head>
<body>
<div class="share-header">
<nav>
<a class="logo" href="/" target="_blank">
<img src="static/landing/logo.svg" height="30px"/>
</a>
<ul>
<li><a href="/" target="_blank">首页</a></li>
<li><a href="app" target="_blank">写笔记</a></li>
</ul>
</nav>
</div>
<div class="share-content stackedit">
<div style="text-align: center; height: 600px;">文章加载中......</div>
</div>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<!-- built files will be auto injected -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?20a1e7a201b42702c49074c87a1f1035";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</body>
</html>