Skip to content

Commit d43469a

Browse files
authored
Merge pull request #12 from frontend-engineering/feat/conflict-files
Feat/conflict files diff view and windows path issue
2 parents d71f1af + 4a8640e commit d43469a

28 files changed

+3507
-396
lines changed

esbuild.config.mjs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import esbuild from "esbuild";
33
import process from "process";
44
import builtins from 'builtin-modules'
55
import cssModulesPlugin from "esbuild-css-modules-plugin"
6-
6+
import {sassPlugin} from 'esbuild-sass-plugin'
7+
import fse from 'fs-extra';
8+
import chalk from 'chalk';
79

810
const banner = `/*
911
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
@@ -81,3 +83,38 @@ esbuild
8183
},
8284
})
8385
.catch(() => process.exit(1));
86+
87+
88+
89+
esbuild
90+
.build({
91+
entryPoints: [
92+
'src/styles/styles.js'
93+
],
94+
plugins: [
95+
sassPlugin(),
96+
cssModulesPlugin({
97+
force: true,
98+
emitDeclarationFile: true,
99+
localsConvention: 'camelCaseOnly',
100+
namedExports: true,
101+
inject: false
102+
})
103+
],
104+
format: "iife",
105+
outdir: '.',
106+
watch: !prod,
107+
minify: true,
108+
bundle: true,
109+
keepNames: true, // 保留函数和变量的原始名称
110+
})
111+
.then(result => {
112+
fse.rmSync('./styles.js')
113+
if (result.errors?.length > 0) {
114+
console.log(chalk.red('bundle style failed: ', result.errors));
115+
} else {
116+
console.log(chalk.green('bundle style success'));
117+
}
118+
})
119+
120+

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "invio",
33
"name": "Invio",
4-
"version": "0.7.6",
4+
"version": "0.7.7",
55
"minAppVersion": "0.13.21",
66
"description": "Export documents as static websites and deploy to AWS S3 or compatible COS. Streamlining Obsidian Sync and Publish, Invio lets you share notes online, retaining data control via self-hosting.",
77
"author": "frontend-engineering",

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "obsidian-invio",
3-
"version": "0.7.6",
3+
"version": "0.7.7",
44
"description": "Publish your obsidian docs online.",
55
"scripts": {
66
"dev": "node esbuild.config.mjs",
@@ -27,6 +27,7 @@
2727
"@microsoft/microsoft-graph-types": "^2.19.0",
2828
"@types/chai": "^4.3.1",
2929
"@types/chai-as-promised": "^7.1.5",
30+
"@types/diff": "^5.0.8",
3031
"@types/js-beautify": "^1.13.3",
3132
"@types/jsdom": "^16.2.14",
3233
"@types/lodash": "^4.14.182",
@@ -45,6 +46,7 @@
4546
"chai-as-promised": "^7.1.1",
4647
"chalk": "^5.3.0",
4748
"cross-env": "^7.0.3",
49+
"css-minify": "^2.0.0",
4850
"dotenv": "^16.0.0",
4951
"downloadjs": "^1.4.7",
5052
"electron": "^18.3.15",
@@ -56,6 +58,7 @@
5658
"jsdom": "^19.0.0",
5759
"mocha": "^9.2.2",
5860
"prettier": "^2.6.2",
61+
"sass": "^1.69.5",
5962
"ts-loader": "^9.2.9",
6063
"ts-node": "^10.7.0",
6164
"tslib": "2.4.0",
@@ -86,6 +89,8 @@
8689
"builtin-modules": "3.3.0",
8790
"classnames": "^2.3.2",
8891
"crypto-browserify": "^3.12.0",
92+
"diff": "^5.1.0",
93+
"diff2html": "^3.4.45",
8994
"dropbox": "^10.28.0",
9095
"electron": "^18.3.15",
9196
"emoji-regex": "^10.1.0",

pnpm-lock.yaml

Lines changed: 1024 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/diff/abstract_diff_view.ts

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import { createTwoFilesPatch } from 'diff';
2+
import { Diff2HtmlConfig, html } from 'diff2html';
3+
// @ts-ignore
4+
import { App, Modal, TFile, Notice, setTooltip } from 'obsidian';
5+
import type { vItem, vRecoveryItem, vSyncItem } from './interfaces';
6+
import type InvioPlugin from '../main';
7+
import type { LangType, LangTypeAndAuto, TransItemType } from "../i18n";
8+
9+
type TviewOutputFormat = `side-by-side` | `line-by-line`
10+
export type TDiffType = `LocalToRemote` | `RemoteToLocal` | `Conflict`
11+
12+
export default abstract class DiffView extends Modal {
13+
plugin: InvioPlugin;
14+
app: App;
15+
file: TFile;
16+
leftVList: vItem[];
17+
rightVList: vItem[];
18+
leftActive: number;
19+
rightActive: number;
20+
rightContent: string;
21+
leftContent: string;
22+
leftName: string;
23+
rightName: string;
24+
syncHistoryContentContainer: HTMLElement;
25+
leftHistory: HTMLElement[];
26+
rightHistory: HTMLElement[];
27+
htmlConfig: Diff2HtmlConfig;
28+
ids: { left: number; right: number };
29+
silentClose: boolean;
30+
viewOutputFormat: TviewOutputFormat;
31+
fileChangedHook: (file: TFile) => void;
32+
cancelHook: () => void;
33+
34+
constructor(plugin: InvioPlugin, app: App, file: TFile, changedHook: (file: TFile) => void, cancelHook: () => void) {
35+
super(app);
36+
this.plugin = plugin;
37+
this.app = app;
38+
this.file = file;
39+
this.modalEl.addClasses(['mod-sync-history', 'diff']);
40+
this.leftVList = [];
41+
this.rightVList = [];
42+
this.rightActive = 0;
43+
this.leftActive = 1;
44+
this.leftName = '';
45+
this.rightName = '';
46+
this.rightContent = '';
47+
this.leftContent = '';
48+
this.ids = { left: 0, right: 0 };
49+
this.fileChangedHook = changedHook;
50+
this.cancelHook = cancelHook;
51+
this.silentClose = false;
52+
this.viewOutputFormat = 'side-by-side';
53+
54+
// @ts-ignore
55+
this.leftHistory = [null];
56+
// @ts-ignore
57+
this.rightHistory = [null];
58+
this.htmlConfig = {
59+
drawFileList: true,
60+
diffStyle: 'word',
61+
matchWordsThreshold: 0.25,
62+
outputFormat: this.viewOutputFormat,
63+
renderNothingWhenEmpty: false,
64+
rawTemplates: {
65+
'line-by-line-file-diff': `<div id="{{fileHtmlId}}" class="d2h-file-wrapper" data-lang="{{file.language}}">
66+
<div class="d2h-file-header">
67+
{{{filePath}}}
68+
</div>
69+
<div class="d2h-file-diff">
70+
<div class="d2h-code-wrapper">
71+
<table class="d2h-diff-table">
72+
<tbody class="d2h-diff-tbody">
73+
{{{diffs}}}
74+
</tbody>
75+
</table>
76+
</div>
77+
</div>
78+
</div>`,
79+
'side-by-side-file-diff': `<div id="{{fileHtmlId}}" class="d2h-file-wrapper" data-lang="{{file.language}}">
80+
<div class="d2h-file-header">
81+
{{{filePath}}}
82+
</div>
83+
<div class="d2h-files-diff">
84+
<div class="d2h-file-side-diff">
85+
<div class="d2h-code-title">${this.leftName}</div>
86+
<div class="d2h-code-wrapper">
87+
<table class="d2h-diff-table">
88+
<tbody class="d2h-diff-tbody">
89+
{{{diffs.left}}}
90+
</tbody>
91+
</table>
92+
</div>
93+
</div>
94+
<div class="d2h-file-side-diff">
95+
<div class="d2h-code-title">${this.rightName}</div>
96+
<div class="d2h-code-wrapper">
97+
<table class="d2h-diff-table">
98+
<tbody class="d2h-diff-tbody">
99+
{{{diffs.right}}}
100+
</tbody>
101+
</table>
102+
</div>
103+
</div>
104+
</div>
105+
</div>`,
106+
'generic-empty-diff': `<tr>
107+
<td class="{{CSSLineClass.INFO}}">
108+
<div class="{{contentClass}}">
109+
File without changes
110+
</div>
111+
</td>
112+
</tr>`
113+
}
114+
};
115+
this.containerEl.addClass('diff');
116+
// @ts-ignore
117+
const contentParent = this.contentEl.createDiv({
118+
cls: ['sync-history-content-container-parent'],
119+
});
120+
121+
this.syncHistoryContentContainer = contentParent.createDiv({
122+
cls: ['sync-history-content-container', 'diff'],
123+
})
124+
}
125+
126+
onClose(): void {
127+
if (!this.silentClose) {
128+
this.cancelHook && this.cancelHook()
129+
}
130+
}
131+
132+
reload(config?: Partial<Diff2HtmlConfig>) {
133+
this.syncHistoryContentContainer.innerHTML =
134+
this.getDiff(config) as string;
135+
}
136+
137+
public getDiff(config?: Diff2HtmlConfig): string {
138+
// the second type is needed for the Git view, it reimplements getDiff
139+
// get diff
140+
const uDiff = createTwoFilesPatch(
141+
this.file.basename,
142+
this.file.basename,
143+
this.leftContent,
144+
this.rightContent
145+
);
146+
147+
// create HTML from diff
148+
const diff = html(uDiff, {
149+
...this.htmlConfig,
150+
...(config || {})
151+
});
152+
return diff;
153+
}
154+
155+
public makeHistoryLists(warning: string): void {
156+
// create both history lists
157+
this.rightHistory = this.createHistory(this.contentEl);
158+
}
159+
160+
public t(x: TransItemType, vars?: any) {
161+
return this.plugin.i18n.t(x, vars);
162+
}
163+
164+
public async changeFileAndCloseModal(contents: string) {
165+
await this.app.vault.modify(this.file, contents);
166+
this.fileChangedHook && this.fileChangedHook(this.file);
167+
this.silentClose = true
168+
169+
setTimeout(() => {
170+
console.log('close modal after 500ms')
171+
this.close()
172+
}, 500)
173+
}
174+
175+
private createHistory(
176+
el: HTMLElement,
177+
): HTMLElement[] {
178+
const syncHistoryListContainer = el.createDiv({
179+
cls: ['sync-history-list-container', 'edit-list-show'],
180+
});
181+
182+
const syncHistoryList = syncHistoryListContainer.createDiv({
183+
cls: 'sync-history-list',
184+
});
185+
const title = syncHistoryList.createDiv({
186+
cls: ['sync-history-list-item', 'title'],
187+
text: this.t('diff_edit_list')
188+
});
189+
190+
const setVerBtn = syncHistoryListContainer.createEl('button', {
191+
cls: ['mod-cta', 'btn'],
192+
text: this.t('diff_reset_ver_btn')
193+
});
194+
setTooltip(setVerBtn, 'Click to replace with current selected version', {
195+
placement: 'top',
196+
});
197+
setVerBtn.addEventListener('click', (e) => {
198+
e.preventDefault();
199+
this.changeFileAndCloseModal(this.rightContent);
200+
new Notice(
201+
`The ${this.file.basename} file has been overwritten with the selected version.`
202+
);
203+
});
204+
return [syncHistoryListContainer, syncHistoryList];
205+
}
206+
207+
public basicHtml(diff: string, diffType: TDiffType): void {
208+
// set title
209+
this.titleEl.setText(diffType === `LocalToRemote` ? this.t('diff_view_local_title') : (diffType === `RemoteToLocal` ? this.t('diff_view_remote_title') : this.t('diff_view_conflict_title')));
210+
// add diff to container
211+
this.syncHistoryContentContainer.innerHTML = diff;
212+
213+
// add history lists and diff to DOM
214+
// this.contentEl.appendChild(this.leftHistory[0]);
215+
this.contentEl.appendChild(this.syncHistoryContentContainer?.parentNode);
216+
if (diffType === `LocalToRemote`) {
217+
this.contentEl.appendChild(this.rightHistory[0]);
218+
}
219+
}
220+
221+
222+
public makeMoreGeneralHtml(): void {
223+
// highlight initial two versions
224+
this.rightVList[0].html.addClass('is-active');
225+
// keep track of highlighted versions
226+
this.rightActive = 0;
227+
this.leftActive = 1;
228+
}
229+
230+
public async generateVersionListener(
231+
div: HTMLDivElement,
232+
currentVList: vItem[],
233+
currentActive: number,
234+
): Promise<vItem> {
235+
// the exact return type depends on the type of currentVList, it is either vSyncItem or vRecoveryItem
236+
// formerly active left/right version
237+
const currentSideOldVersion = currentVList[currentActive];
238+
// get the HTML of the new version to set it active
239+
const idx = Number(div.id);
240+
const clickedEl: vItem = currentVList[idx];
241+
div.addClass('is-active');
242+
this.rightActive = idx;
243+
// make old not active
244+
currentSideOldVersion.html.classList.remove('is-active');
245+
return clickedEl;
246+
}
247+
}

0 commit comments

Comments
 (0)