mirror of
https://gitee.com/mafgwo/stackedit
synced 2024-11-16 11:42:23 +08:00
支持分享功能
This commit is contained in:
parent
58c9144612
commit
ae828cfb56
|
@ -71,6 +71,7 @@ StackEdit中文版
|
|||
- 支持预览区域选择主题样式(2022-12-04)
|
||||
- Gitlab的支持优化(2023-02-23)
|
||||
- 导出HTML、PDF支持带预览主题导出(2023-02-26)
|
||||
- 支持分享文档(2023-03-30)
|
||||
|
||||
## 国外开源版本弊端:
|
||||
- 作者已经不维护了
|
||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "stackedit",
|
||||
"version": "5.15.17",
|
||||
"version": "5.15.19",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "stackedit",
|
||||
"version": "5.15.18",
|
||||
"version": "5.15.19",
|
||||
"description": "免费, 开源, 功能齐全的 Markdown 编辑器",
|
||||
"author": "Benoit Schweblin, 豆萁",
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -61,13 +61,15 @@ module.exports = (app) => {
|
|||
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: '1d',
|
||||
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')));
|
||||
|
||||
// Serve static resources
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
|
|
|
@ -64,6 +64,8 @@ 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';
|
||||
|
@ -131,6 +133,8 @@ export default {
|
|||
GiteeSaveModal,
|
||||
GiteeWorkspaceModal,
|
||||
GiteePublishModal,
|
||||
GiteeGistSyncModal,
|
||||
GiteeGistPublishModal,
|
||||
GitlabAccountModal,
|
||||
GitlabOpenModal,
|
||||
GitlabPublishModal,
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
<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>
|
||||
|
@ -21,6 +24,8 @@ 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';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -56,12 +61,16 @@ export default {
|
|||
value: 'custom',
|
||||
}],
|
||||
baseCss: '',
|
||||
sharing: false,
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('theme', [
|
||||
'currPreviewTheme',
|
||||
'customPreviewThemeStyle',
|
||||
]),
|
||||
...mapGetters('publishLocation', {
|
||||
publishLocations: 'current',
|
||||
}),
|
||||
selectedTheme() {
|
||||
return {
|
||||
value: this.currPreviewTheme || 'default',
|
||||
|
@ -84,6 +93,43 @@ export default {
|
|||
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 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>
|
||||
|
@ -94,7 +140,7 @@ export default {
|
|||
.preview-in-page-buttons {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: -68px;
|
||||
right: -98px;
|
||||
height: 34px;
|
||||
padding: 5px;
|
||||
background-color: rgba(84, 96, 114, 0.4);
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<div v-for="token in githubTokens" :key="token.sub">
|
||||
<menu-entry @click.native="publishGist(token)">
|
||||
<icon-provider slot="icon" provider-id="gist"></icon-provider>
|
||||
<div>发布到 Gist</div>
|
||||
<div>发布到 GitHubGist</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
<menu-entry @click.native="publishGithub(token)">
|
||||
|
@ -53,6 +53,11 @@
|
|||
</menu-entry>
|
||||
</div>
|
||||
<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)">
|
||||
<icon-provider slot="icon" provider-id="gitee"></icon-provider>
|
||||
<div>发布到 Gitee</div>
|
||||
|
@ -289,8 +294,9 @@ export default {
|
|||
publishBloggerPage: publishModalOpener('bloggerPagePublish', 'publishToBloggerPage'),
|
||||
publishDropbox: publishModalOpener('dropboxPublish', 'publishToDropbox'),
|
||||
publishGithub: publishModalOpener('githubPublish', 'publishToGithub'),
|
||||
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
|
||||
publishGist: publishModalOpener('gistPublish', 'publishToGist'),
|
||||
publishGitee: publishModalOpener('giteePublish', 'publishToGitee'),
|
||||
publishGiteeGist: publishModalOpener('giteeGistPublish', 'publishGiteeGist'),
|
||||
publishGitlab: publishModalOpener('gitlabPublish', 'publishToGitlab'),
|
||||
publishGitea: publishModalOpener('giteaPublish', 'publishToGitea'),
|
||||
publishGoogleDrive: publishModalOpener('googleDrivePublish', 'publishToGoogleDrive'),
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
</menu-entry>
|
||||
<menu-entry @click.native="saveGist(token)">
|
||||
<icon-provider slot="icon" provider-id="gist"></icon-provider>
|
||||
<div>在Gist上保存</div>
|
||||
<div>在GitHubGist上保存</div>
|
||||
<span>{{token.name}}</span>
|
||||
</menu-entry>
|
||||
</div>
|
||||
|
@ -61,6 +61,11 @@
|
|||
<div>在Gitee上保存</div>
|
||||
<span>{{token.name}}</span>
|
||||
</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 v-for="token in gitlabTokens" :key="token.sub">
|
||||
<menu-entry @click.native="openGitlab(token)">
|
||||
|
@ -330,6 +335,12 @@ export default {
|
|||
badgeSvc.addBadge('saveOnGist');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async saveGiteeGist(token) {
|
||||
try {
|
||||
await openSyncModal(token, 'giteeGistSync');
|
||||
badgeSvc.addBadge('saveOnGiteeGist');
|
||||
} catch (e) { /* cancel */ }
|
||||
},
|
||||
async openGitlab(token) {
|
||||
try {
|
||||
const syncLocation = await store.dispatch('modal/open', {
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<div class="modal__image">
|
||||
<icon-upload></icon-upload>
|
||||
</div>
|
||||
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> is published to the following location(s):</p>
|
||||
<p v-else><b>{{currentFileName}}</b> is not published yet.</p>
|
||||
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> 被发布到了以下位置:</p>
|
||||
<p v-else><b>{{currentFileName}}</b> 还没有被发布.</p>
|
||||
<div>
|
||||
<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">
|
||||
|
@ -26,7 +26,7 @@
|
|||
{{location.url}}
|
||||
</div>
|
||||
<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>
|
||||
</button>
|
||||
<a class="publish-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'打开位置'">
|
||||
|
@ -34,6 +34,19 @@
|
|||
</a>
|
||||
</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 class="modal__info" v-if="publishLocations.length">
|
||||
|
@ -75,6 +88,16 @@ export default {
|
|||
store.commit('publishLocation/deleteItem', location.id);
|
||||
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>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<modal-inner aria-label="发布到Gist">
|
||||
<modal-inner aria-label="发布到GitHubGist">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gist"></icon-provider>
|
||||
</div>
|
||||
<p>发布<b> {{CurrentFileName}} </b>到<b>Gist</b>。</p>
|
||||
<form-entry label="Filename" error="filename">
|
||||
<p>发布<b> {{CurrentFileName}} </b>到<b>GitHubGist</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">
|
||||
|
@ -15,10 +15,10 @@
|
|||
</label>
|
||||
</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()">
|
||||
<div class="form-entry__info">
|
||||
如果文件存在于Gist中,则将被覆盖。
|
||||
如果文件存在于GitHubGist中,则将被覆盖。
|
||||
</div>
|
||||
</form-entry>
|
||||
<form-entry label="Template">
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<modal-inner aria-label="与 Gist 同步">
|
||||
<modal-inner aria-label="与 GitHubGist 同步">
|
||||
<div class="modal__content">
|
||||
<div class="modal__image">
|
||||
<icon-provider provider-id="gist"></icon-provider>
|
||||
</div>
|
||||
<p>将<b> {{currentFileName}} </b>保存到<b>Gist</b>并保持同步。</p>
|
||||
<form-entry label="Filename" error="filename">
|
||||
<p>将<b> {{currentFileName}} </b>保存到<b>GitHubGist</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">
|
||||
|
@ -15,10 +15,10 @@
|
|||
</label>
|
||||
</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()">
|
||||
<div class="form-entry__info">
|
||||
如果文件存在于Gist中,则将被覆盖。
|
||||
如果文件存在于GitHubGist中,则将被覆盖。
|
||||
</div>
|
||||
</form-entry>
|
||||
</div>
|
||||
|
|
79
src/components/modals/providers/GiteeGistPublishModal.vue
Normal file
79
src/components/modals/providers/GiteeGistPublishModal.vue
Normal 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>
|
64
src/components/modals/providers/GiteeGistSyncModal.vue
Normal file
64
src/components/modals/providers/GiteeGistSyncModal.vue
Normal 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>
|
|
@ -316,6 +316,11 @@ export default [
|
|||
'GitHub保存',
|
||||
'使用“同步”菜单将文件保存在GitHub仓库中。',
|
||||
),
|
||||
new Feature(
|
||||
'saveOnGist',
|
||||
'GitHubGist保存',
|
||||
'使用“同步”菜单将文件保存在GitHubGist中。',
|
||||
),
|
||||
new Feature(
|
||||
'openFromGitee',
|
||||
'Gitee阅读器',
|
||||
|
@ -327,9 +332,9 @@ export default [
|
|||
'使用“同步”菜单将文件保存在Gitee仓库中。',
|
||||
),
|
||||
new Feature(
|
||||
'saveOnGist',
|
||||
'Gist保存',
|
||||
'使用“同步”菜单将文件保存在GIST中。',
|
||||
'saveOnGiteeGist',
|
||||
'GiteeGist保存',
|
||||
'使用“同步”菜单将文件保存在GiteeGist中。',
|
||||
),
|
||||
new Feature(
|
||||
'openFromGitlab',
|
||||
|
@ -405,14 +410,19 @@ export default [
|
|||
),
|
||||
new Feature(
|
||||
'publishToGist',
|
||||
'Gist发布',
|
||||
'使用“发布”菜单将文件发布到GIST。',
|
||||
'GitHubGist发布',
|
||||
'使用“发布”菜单将文件发布到GitHubGist。',
|
||||
),
|
||||
new Feature(
|
||||
'publishToGitee',
|
||||
'Gitee发布',
|
||||
'使用“发布”菜单将文件发布到Gitee仓库。',
|
||||
),
|
||||
new Feature(
|
||||
'publishToGiteeGist',
|
||||
'GiteeGist发布',
|
||||
'使用“发布”菜单将文件发布到GiteeGist。',
|
||||
),
|
||||
new Feature(
|
||||
'publishToGitlab',
|
||||
'GitLab发布',
|
||||
|
|
|
@ -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(
|
||||
`<p>您必须使用 Google 登录才能开始评论。</p>
|
||||
<div class="modal__info"><b>注意:</b> 这将同步您的主文档空间。</div>`,
|
||||
|
|
|
@ -29,6 +29,7 @@ export default {
|
|||
return 'couchdb';
|
||||
case 'giteeAppData':
|
||||
case 'giteeWorkspace':
|
||||
case 'giteegist':
|
||||
return 'gitee';
|
||||
default:
|
||||
return this.providerId;
|
||||
|
|
3
src/icons/Share.vue
Normal file
3
src/icons/Share.vue
Normal 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>
|
|
@ -64,6 +64,7 @@ import FindReplace from './FindReplace';
|
|||
import SelectTheme from './SelectTheme';
|
||||
import Copy from './Copy';
|
||||
import Ellipsis from './Ellipsis';
|
||||
import Share from './Share';
|
||||
|
||||
Vue.component('iconProvider', Provider);
|
||||
Vue.component('iconFormatBold', FormatBold);
|
||||
|
@ -130,3 +131,4 @@ Vue.component('iconFindReplace', FindReplace);
|
|||
Vue.component('iconSelectTheme', SelectTheme);
|
||||
Vue.component('iconCopy', Copy);
|
||||
Vue.component('iconEllipsis', Ellipsis);
|
||||
Vue.component('iconShare', Share);
|
||||
|
|
95
src/services/providers/giteeGistProvider.js
Normal file
95
src/services/providers/giteeGistProvider.js
Normal 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);
|
||||
// },
|
||||
});
|
|
@ -346,8 +346,8 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* https://developer.gitee.com/v3/gists/#create-a-gist
|
||||
* https://developer.gitee.com/v3/gists/#edit-a-gist
|
||||
* https://gitee.com/api/v5/swagger#/postV5Gists
|
||||
* https://gitee.com/api/v5/swagger#/patchV5GistsId
|
||||
*/
|
||||
async uploadGist({
|
||||
token,
|
||||
|
@ -357,8 +357,7 @@ export default {
|
|||
isPublic,
|
||||
gistId,
|
||||
}) {
|
||||
const refreshedToken = await this.refreshToken(token);
|
||||
const { body } = await request(refreshedToken, gistId ? {
|
||||
const { body } = await request(token, gistId ? {
|
||||
method: 'PATCH',
|
||||
url: `https://gitee.com/api/v5/gists/${gistId}`,
|
||||
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({
|
||||
token,
|
||||
gistId,
|
||||
filename,
|
||||
}) {
|
||||
const refreshedToken = await this.refreshToken(token);
|
||||
const result = (await request(refreshedToken, {
|
||||
url: `https://gitee.com/api/v5/gists/${gistId}`,
|
||||
const result = (await request(token, {
|
||||
url: `https://api.github.com/gists/${gistId}`,
|
||||
})).body.files[filename];
|
||||
if (!result) {
|
||||
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({
|
||||
token,
|
||||
gistId,
|
||||
}) {
|
||||
const refreshedToken = await this.refreshToken(token);
|
||||
const { body } = await request(refreshedToken, {
|
||||
const { body } = await request(token, {
|
||||
url: `https://gitee.com/api/v5/gists/${gistId}/commits`,
|
||||
});
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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 currentFile = store.getters['file/current'];
|
||||
publishLocation.fileId = currentFile.id;
|
||||
|
@ -157,8 +163,7 @@ const createPublishLocation = (publishLocation, featureId) => {
|
|||
return;
|
||||
}
|
||||
}
|
||||
const publishLocationToStore = await publish(publishLocation, commitMsg);
|
||||
workspaceSvc.addPublishLocation(publishLocationToStore);
|
||||
await publishLocationAndStore(publishLocation, commitMsg);
|
||||
store.dispatch('notification/info', `添加了一个新的发布位置 "${currentFile.name}".`);
|
||||
if (featureId) {
|
||||
badgeSvc.addBadge(featureId);
|
||||
|
@ -169,5 +174,6 @@ const createPublishLocation = (publishLocation, featureId) => {
|
|||
|
||||
export default {
|
||||
requestPublish,
|
||||
publishLocationAndStore,
|
||||
createPublishLocation,
|
||||
};
|
||||
|
|
165
static/landing/share.html
Normal file
165
static/landing/share.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user