Skip to content

Commit

Permalink
feat: github flavor markdown
Browse files Browse the repository at this point in the history
copy to clipboard and avtiveTextEdior
  • Loading branch information
fangbinwei committed May 24, 2020
1 parent cb5e4e9 commit f7b95ec
Show file tree
Hide file tree
Showing 9 changed files with 937 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ out
node_modules
.vscode-test/
*.vsix
.DS_Store
32 changes: 22 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,53 @@
"Other"
],
"activationEvents": [
"onCommand:elan.uploadFromClipboard"
"onCommand:elan.uploadFromClipboard",
"onCommand:elan.uploadFromExplorer"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "elan.uploadFromClipboard",
"title": "Elan: upload image from clipboard"
},
{
"command": "elan.uploadFromExplorer",
"title": "Elan: upload image from explorer"
}
],
"configuration": {
"type": "object",
"title": "Elan",
"properties": {
"elan.accessKeyId": {
"elan.aliyun.accessKeyId": {
"type": "string",
"default": ""
},
"elan.accessKeySecret": {
"elan.aliyun.accessKeySecret": {
"type": "string",
"default": ""
},
"elan.bucket": {
"elan.aliyun.bucket": {
"type": "string",
"default": ""
},
"elan.area": {
"elan.aliyun.region": {
"type": "string",
"default": ""
},
"elan.subPath": {
"elan.aliyun.subPath": {
"type": "string",
"default": ""
},
"elan.customUrl": {
"elan.outputFormat": {
"type": "string",
"default": ""
"markdownDescription": "- `${fileName}`: Filename of uploaded file.\n- `${uploadName}`: Custom defined config. \n- `${url}`: After a file is uploaded successfully, the OSS sends a callback request to this URL.",
"default": "![${uploadName}](${url})"
},
"elan.uploadName": {
"type": "string",
"markdownDescription": "- `${fileName}`: Filename of uploaded file.\n-`${extName}`: Filename extension of uploaded file.",
"default": "${fileName}${extName}"
}
}
}
Expand All @@ -64,7 +74,6 @@
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/mocha": "^7.0.2",
"@types/node": "^13.11.0",
"@types/vscode": "^1.45.0",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
Expand All @@ -80,6 +89,9 @@
"vscode-test": "^1.3.0"
},
"dependencies": {
"@types/ali-oss": "^6.0.5",
"ali-oss": "^6.8.0",
"date-fns": "^2.14.0",
"execa": "^4.0.1"
}
}
12 changes: 8 additions & 4 deletions src/commands/uploadFromClipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ import path from 'path'
import fs from 'fs'
import execa from 'execa'
import os from 'os'
import { uploadFsPaths } from '../utils/uploader/index'
import { uploadUris } from '../utils/uploader/index'
import vscode from 'vscode'
import { format } from 'date-fns'

interface ClipboardImage {
noImage: boolean
data: string
}

export default async function uploadImage(): Promise<void> {
const targetPath = path.resolve(os.tmpdir(), Date.now().toString())
const targetPath = path.resolve(
os.tmpdir(),
format(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.png'
)
const clipboardImage = await saveClipboardImageToFile(targetPath)
if (!clipboardImage) return
if (clipboardImage.noImage) {
Logger.showErrorMessage('no image')
Logger.showErrorMessage('The clipboard does not contain image data.')
return
}
await uploadFsPaths([vscode.Uri.file(targetPath)])
await uploadUris([vscode.Uri.file(targetPath)])
}

export async function saveClipboardImageToFile(
Expand Down
4 changes: 2 additions & 2 deletions src/commands/uploadFromExplorer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import vscode from 'vscode'
import { uploadFsPaths } from '../utils/uploader/index'
import { uploadUris } from '../utils/uploader/index'

export default async function uploadImageFromExplorer(): Promise<void> {
const result = await vscode.window.showOpenDialog({
Expand All @@ -10,5 +10,5 @@ export default async function uploadImageFromExplorer(): Promise<void> {
})
if (!result) return

await uploadFsPaths(result)
await uploadUris(result)
}
38 changes: 19 additions & 19 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import vscode from 'vscode'
import uploadFromClipboard from './commands/uploadFromClipboard'
import uploadFromExplorer from './commands/uploadFromExplorer'
import Logger from './utils/log'

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "helloworld" is now active!');
export function activate(context: vscode.ExtensionContext): void {
Logger.channel = vscode.window.createOutputChannel('Elan')

// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
// The code you place here will be executed every time your command is executed

// Display a message box to the user
vscode.window.showInformationMessage('Hello World from helloworld!');
});

context.subscriptions.push(disposable);
const disposable = [
vscode.commands.registerCommand(
'elan.uploadFromClipboard',
uploadFromClipboard
),
vscode.commands.registerCommand(
'elan.uploadFromExplorer',
uploadFromExplorer
)
]
context.subscriptions.push(...disposable)
}

// this method is called when your extension is deactivated
export function deactivate() {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function deactivate(): void {}
5 changes: 4 additions & 1 deletion src/utils/log.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import vscode from 'vscode'
import { format } from 'date-fns'

export default class Logger {
static channel: vscode.OutputChannel

static log(message: string): void {
if (this.channel) {
this.channel.appendLine(`[${new Date().toString()}] ${message}`)
this.channel.appendLine(
`[${format(new Date(), 'MM-dd HH:mm:ss')}]: ${message}`
)
}
}

Expand Down
169 changes: 169 additions & 0 deletions src/utils/uploader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import vscode from 'vscode'
import OSS from 'ali-oss'
import path from 'path'
import Logger from '../log'
import { TemplateStore } from './templateStore'

let uploader: Uploader | null = null

declare global {
interface PromiseConstructor {
allSettled(
promises: Array<Promise<unknown>>
): Promise<
Array<{
status: 'fulfilled' | 'rejected'
value?: unknown
reason?: unknown
}>
>
}
}

interface WrapError {
raw: unknown
imageName: string
}

interface UploadingProgress {
progress: vscode.Progress<{ message?: string; increment?: number }>
progressResolve: (value?: unknown) => void
progressReject: (value?: unknown) => void
}

function getUploadingProgress(title = 'Uploading image'): UploadingProgress {
let progressResolve, progressReject, progress
vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title
},
(p) => {
return new Promise((resolve, reject) => {
progressResolve = resolve
progressReject = reject
progress = p
})
}
)
if (!progress || !progressResolve || !progressReject)
throw new Error('Failed to init vscode progress')
return {
progress,
progressResolve,
progressReject
}
}

export async function uploadUris(uris: vscode.Uri[]): Promise<void> {
const uploader = getUploader()
const { progress, progressResolve } = getUploadingProgress(
`Uploading ${uris.length} image(s)`
)
const clipboard: string[] = []

let finished = 0
const urisPut = uris.map((uri) => {
const templateStore = new TemplateStore()
const extName = path.extname(uri.fsPath)
const name = path.basename(uri.fsPath, extName)

templateStore.set('fileName', name)
templateStore.set('extName', extName)
const uploadName = templateStore.transform('uploadName')

const u = uploader.put(uploadName, uri.fsPath)
u.then((putObjectResult) => {
progress.report({
message: `(${++finished} / ${uris.length})`,
increment: Math.ceil(100 / uris.length)
})

templateStore.set('url', putObjectResult.url)
clipboard.push(templateStore.transform('outputFormat'))

return putObjectResult
}).catch((err) => {
const wrapError = {
raw: err,
imageName: name
}
return wrapError
})
return u
})

const settled = await Promise.allSettled(urisPut)
const rejects = settled.filter((r) => {
return r.status === 'rejected'
})

if (!rejects.length) {
progress.report({
message: 'Finish.'
})

setTimeout(() => {
progressResolve()
}, 1000)
} else {
progress.report({
message: `${uris.length - rejects.length} images uploaded.`
})
setTimeout(() => {
progressResolve()
Logger.showErrorMessage(
`Failed to upload these images: ${rejects
.map((r) => {
return (r.reason as WrapError).imageName
})
.join(',')}`
)
}, 1000)
}

afterUpload(clipboard)
}

function afterUpload(clipboard: string[]): void {
if (!clipboard.length) return
const GFM = clipboard.join('\n\n')
vscode.env.clipboard.writeText(GFM)
const activeTextEditor = vscode.window.activeTextEditor
if (!activeTextEditor) return

activeTextEditor.edit((textEditorEdit) => {
textEditorEdit.insert(activeTextEditor.selection.active, GFM)
})
}

export function getUploader(): Uploader {
return uploader || (uploader = new Uploader())
}

function initOSSOptions(): OSS.Options {
const config = vscode.workspace.getConfiguration('elan')
const aliyunConfig = config.get<OSS.Options>('aliyun', {
accessKeyId: '',
accessKeySecret: ''
})
return {
accessKeyId: aliyunConfig.accessKeyId.trim(),
accessKeySecret: aliyunConfig.accessKeySecret.trim(),
bucket: aliyunConfig.bucket?.trim(),
region: aliyunConfig.region?.trim()
}
}

class Uploader {
client: OSS
constructor() {
this.client = new OSS(initOSSOptions())
vscode.workspace.onDidChangeConfiguration(() => {
this.client = new OSS(initOSSOptions())
})
}
async put(name: string, fsPath: string): Promise<OSS.PutObjectResult> {
return this.client.put(name, fsPath)
}
}
Loading

0 comments on commit f7b95ec

Please sign in to comment.