Skip to content

Commit

Permalink
feat: add local http server
Browse files Browse the repository at this point in the history
  • Loading branch information
ylc395 committed Nov 18, 2021
1 parent d1a0bf3 commit c9384e5
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 10 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/lodash": "^4.14.170",
"@types/node": "^14.0.14",
"@types/sanitize-html": "^2.3.2",
"@types/serve-handler": "^6.1.1",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"@vue/compiler-sfc": "^3.1.2",
Expand Down Expand Up @@ -77,6 +78,7 @@
"lowdb": "^2.1.0",
"moment": "^2.29.1",
"sanitize-html": "^2.4.0",
"serve-handler": "^6.1.3",
"slugify": "^1.5.3",
"tsyringe": "^4.5.0",
"valid-filename": "^4.0.0",
Expand Down
5 changes: 4 additions & 1 deletion src/domain/service/AppService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InjectionKey, reactive } from 'vue';
import { container, InjectionToken, singleton } from 'tsyringe';
import { isEqual, last, pull } from 'lodash';
import { PluginDataRepository } from '../repository/PluginDataRepository';
import { PreviewService } from './PreviewService';

export interface UI {
openModal: (modal: {
Expand Down Expand Up @@ -52,6 +53,7 @@ export class AppService {
});
private readonly joplin = container.resolve(joplinToken);
private readonly pluginDataRepository = new PluginDataRepository();
private readonly previewService = container.resolve(PreviewService);

private readonly ui = container.resolve(uiToken);
showingQuitButton = true;
Expand Down Expand Up @@ -98,7 +100,8 @@ export class AppService {
return this.ui.openModal(...args);
}

quitApp() {
async quitApp() {
await this.previewService.reset();
return this.joplin.quit();
}

Expand Down
31 changes: 31 additions & 0 deletions src/domain/service/PreviewService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { container, singleton, InjectionToken } from 'tsyringe';
import { ref, Ref, InjectionKey } from 'vue';

export interface Server {
start(): Promise<number>;
close(): Promise<void>;
}

export const serverToken: InjectionToken<Server> = Symbol();

export const token: InjectionKey<PreviewService> = Symbol();

@singleton()
export class PreviewService {
private readonly server = container.resolve(serverToken);
readonly port: Ref<number | undefined> = ref();
readonly message = ref('');
async startPreviewing() {
try {
this.port.value = await this.server.start();
} catch (error) {
this.message.value = (error as Error).message;
}
}

reset() {
this.message.value = '';
this.port.value = undefined;
return this.server.close();
}
}
45 changes: 45 additions & 0 deletions src/driver/server/joplinPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import handler from 'serve-handler';
import http from 'http';
import { getOutputDir } from '../generator/joplinPlugin/pathHelper';

export class HttpServer {
private server?: http.Server;
async start() {
const outputDir = await getOutputDir();
this.server = http.createServer((request, response) =>
handler(request, response, {
public: outputDir,
}),
);
let port = 3000;

return new Promise<number>((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.server!.on('error', (e: Error & { code: string }) => {
if (e.code === 'EADDRINUSE') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.server!.listen(++port, () => resolve(port));
} else {
reject(e.message);
}
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.server!.listen(port, () => resolve(port));
});
}

close() {
return new Promise<void>((resolve, reject) => {
if (!this.server) {
return;
}
this.server.close((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
}
19 changes: 19 additions & 0 deletions src/driver/server/webviewApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { container } from 'tsyringe';
import { serverToken } from 'domain/service/PreviewService';

export interface ServerRequest {
event: 'startServer' | 'closeServer';
}

declare const webviewApi: {
postMessage: <T>(payload: ServerRequest) => Promise<T>;
};

container.registerInstance(serverToken, {
start() {
return webviewApi.postMessage({ event: 'startServer' });
},
close() {
return webviewApi.postMessage({ event: 'closeServer' });
},
});
17 changes: 11 additions & 6 deletions src/driver/webview/app/Publisher/GeneratingModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { computed, defineComponent, inject } from 'vue';
import { Modal, Progress, Result, Button } from 'ant-design-vue';
import { token as publishToken } from 'domain/service/PublishService';
import { token as previewToken } from 'domain/service/PreviewService';
import { activeTabToken } from '../composable';
import { useModalProps } from './composable';
Expand All @@ -20,6 +21,8 @@ export default defineComponent({
inject(publishToken)!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const activeTab = inject(activeTabToken)!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { startPreviewing } = inject(previewToken)!;
const reset = () => refreshGeneratingProgress();
return {
Expand All @@ -33,6 +36,10 @@ export default defineComponent({
await reset();
await publish();
},
preview: async () => {
await reset();
startPreviewing();
},
isGithubInfoValid,
switchToGithubTab: async () => {
await reset();
Expand Down Expand Up @@ -66,12 +73,9 @@ export default defineComponent({
</p>
<p class="break-all text-black font-semibold">{{ outputDir }}</p>
<p>
You can <strong>start a local HTTP server</strong> to preview the generated site(<a
target="_blank"
href="https://github.com/ylc395/joplin-plugin-pages-publisher/wiki/How-to-preview-before-publishing"
>I don't know how to do it</a
>)<template v-if="isGithubInfoValid"
>, or/and you can <strong>click "Publish" button</strong> to publish them to Github
You can click <strong>Preview</strong> button to preview the generated site<template
v-if="isGithubInfoValid"
>, or you can <strong>click "Publish" button</strong> to publish them to Github
directly.</template
><template v-else>. </template>
</p>
Expand All @@ -83,6 +87,7 @@ export default defineComponent({
</template>
<template #extra>
<Button v-if="progress.result" @click="reset">Confirm</Button>
<Button v-if="progress.result === 'success'" @click="preview">Preview</Button>
<Button
v-if="progress.result === 'success' && isGithubInfoValid"
type="primary"
Expand Down
51 changes: 51 additions & 0 deletions src/driver/webview/app/Publisher/PreviewModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script lang="ts">
import { defineComponent, inject } from 'vue';
import { Modal, Result, Button } from 'ant-design-vue';
import { token as previewToken } from 'domain/service/PreviewService';
import { token as publishToken } from 'domain/service/PublishService';
import { useModalProps } from './composable';
export default defineComponent({
components: { Modal, Result, Button },
setup() {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { publish } = inject(publishToken)!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { startPreviewing, port, message, reset } = inject(previewToken)!;
return {
modalProps: useModalProps(),
startPreviewing,
port,
message,
publish: () => {
reset();
publish();
},
reset,
};
},
});
</script>
<template>
<Modal :visible="Boolean(port || message)" v-bind="modalProps">
<Result
class="py-3 px-4"
:status="port ? 'success' : 'error'"
:title="port ? 'Local Server has been started' : 'Unexpected Failure'"
>
<template #subTitle>
<div v-if="port">
You can preview your website in <strong>http:https://localhost:{{ port }}</strong>
</div>
<div v-else class="text-left whitespace-pre-line">
{{ message }}
</div>
</template>
<template #extra>
<Button @click="reset">Confirm</Button>
<Button v-if="port" type="primary" @click="publish">Publish</Button>
</template>
</Result>
</Modal>
</template>
4 changes: 3 additions & 1 deletion src/driver/webview/app/Publisher/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import { defineComponent } from 'vue';
import GeneratingModal from './GeneratingModal.vue';
import PublishingModal from './PublishingModal.vue';
import PreviewModal from './PreviewModal.vue';
export default defineComponent({
components: { GeneratingModal, PublishingModal },
components: { GeneratingModal, PublishingModal, PreviewModal },
});
</script>
<template>
<GeneratingModal />
<PublishingModal />
<PreviewModal />
</template>
2 changes: 2 additions & 0 deletions src/driver/webview/app/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { PageService, token as pageToken } from 'domain/service/PageService';
import { NoteService, token as noteToken } from 'domain/service/NoteService';
import { PublishService, token as publishToken } from 'domain/service/PublishService';
import { AppService, token as appToken, FORBIDDEN } from 'domain/service/AppService';
import { PreviewService, token as previewToken } from 'domain/service/PreviewService';
import ArticleList from './ArticleList/index.vue';
import Site from './Site/index.vue';
Expand Down Expand Up @@ -54,6 +55,7 @@ export default defineComponent({
provide(noteToken, selfish(container.resolve(NoteService)));
provide(siteToken, selfish(container.resolve(SiteService)));
provide(pageToken, selfish(container.resolve(PageService)));
provide(previewToken, selfish(container.resolve(PreviewService)));
provide(publishToken, publishService);
provide(appToken, appService);
Expand Down
1 change: 1 addition & 0 deletions src/driver/webview/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'driver/generator/webviewApi';
import 'driver/joplin/webviewApi';
import 'driver/git/webviewApi';
import 'driver/github/webviewApi';
import 'driver/server/webviewApi';
import './utils/webviewApi';
import App from './app/index.vue';
import { ExceptionService } from 'domain/service/ExceptionService';
Expand Down
8 changes: 8 additions & 0 deletions src/driver/webview/webviewBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ import { loadTheme, loadThemes } from 'driver/themeLoader/joplinPlugin';
import { generateSite, getProgress } from 'driver/generator/joplinPlugin';
import { getOutputDir, getGitRepositoryDir } from 'driver/generator/joplinPlugin/pathHelper';
import { mockNodeFsCall } from 'driver/fs/joplinPlugin';
import type { ServerRequest } from 'driver/server/webviewApi';
import { HttpServer } from 'driver/server/joplinPlugin';
import type Joplin from 'driver/joplin/joplinPlugin';

export default (joplin: Joplin) => {
const db = container.resolve(Db);
const httpServer = new HttpServer();

return (
request:
Expand All @@ -34,6 +37,7 @@ export default (joplin: Joplin) => {
| FsRequest
| GeneratorRequest
| GitRequest
| ServerRequest
| JoplinAction,
) => {
switch (request.event) {
Expand Down Expand Up @@ -75,6 +79,10 @@ export default (joplin: Joplin) => {
return mockNodeFsCall(request.funcName, ...request.args);
case 'getJoplinPluginSetting':
return joplin.getSettingOf(request.key);
case 'startServer':
return httpServer.start();
case 'closeServer':
return httpServer.close();
default:
throw new Error(`unknown bridge request: ${request}`);
}
Expand Down
Loading

0 comments on commit c9384e5

Please sign in to comment.