Skip to content

Commit 7439b9b

Browse files
committed
Check if local files are up to date
1 parent 811918d commit 7439b9b

File tree

10 files changed

+68
-11
lines changed

10 files changed

+68
-11
lines changed

src/client/components/ArticleInfo.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface Props {
1818
errorMessages: string[];
1919
qiitaItemUrl: string | null;
2020
slide: boolean;
21+
isOlderThanRemote: boolean;
2122
}
2223

2324
export const ArticleInfo = ({
@@ -28,6 +29,7 @@ export const ArticleInfo = ({
2829
errorMessages,
2930
qiitaItemUrl,
3031
slide,
32+
isOlderThanRemote,
3133
}: Props) => {
3234
const [isOpen, setIsOpen] = useState(
3335
localStorage.getItem("openInfoState") === "true" ? true : false
@@ -88,6 +90,18 @@ export const ArticleInfo = ({
8890
</InfoItem>
8991
<InfoItem title="スライドモード">{slide ? "ON" : "OFF"}</InfoItem>
9092
</details>
93+
{isOlderThanRemote && (
94+
<div css={errorContentsStyle}>
95+
<p css={errorStyle}>
96+
<MaterialSymbol fill={true} css={exclamationIconStyle}>
97+
error
98+
</MaterialSymbol>
99+
{
100+
"この記事ファイルの内容は、Qiita上の記事より古い可能性があります。"
101+
}
102+
</p>
103+
</div>
104+
)}
91105
{errorMessages.length > 0 && (
92106
<div css={errorContentsStyle}>
93107
{errorMessages.map((errorMessage, index) => (

src/client/components/Header.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Snackbar, Message as SnackbarMessage } from "./Snackbar";
1313
interface Props {
1414
id?: string;
1515
isItemPublishable: boolean;
16+
isOlderThanRemote: boolean;
1617
itemPath: string;
1718
basename: string | null;
1819
handleMobileOpen: () => void;
@@ -21,6 +22,7 @@ interface Props {
2122
export const Header = ({
2223
id,
2324
isItemPublishable,
25+
isOlderThanRemote,
2426
itemPath,
2527
basename,
2628
handleMobileOpen,
@@ -32,6 +34,16 @@ export const Header = ({
3234
const mobileSize = currentWidth <= breakpoint.S;
3335

3436
const handlePublish = () => {
37+
if (isOlderThanRemote) {
38+
if (
39+
!window.confirm(
40+
"この記事はQiita上の記事より古い可能性があります。上書きしますか?"
41+
)
42+
) {
43+
return;
44+
}
45+
}
46+
3547
const params = {
3648
method: "POST",
3749
headers: {

src/client/pages/items/show.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const ItemsShow = () => {
8484
isItemPublishable={
8585
item.modified && item.error_messages.length === 0
8686
}
87+
isOlderThanRemote={item.is_older_than_remote}
8788
itemPath={item.item_path}
8889
id={id}
8990
basename={basename}
@@ -98,6 +99,7 @@ export const ItemsShow = () => {
9899
errorMessages={item.error_messages}
99100
qiitaItemUrl={item.qiita_item_url}
100101
slide={item.slide}
102+
isOlderThanRemote={item.is_older_than_remote}
101103
/>
102104
<div css={articleWrapStyle}>
103105
<Article

src/commands/publish.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export const publish = async (argv: string[]) => {
1313
const args = arg(
1414
{
1515
"--all": Boolean,
16+
"--force": Boolean,
17+
"-f": "--force",
1618
},
1719
{ argv }
1820
);
@@ -41,6 +43,7 @@ export const publish = async (argv: string[]) => {
4143
}
4244

4345
// Validate
46+
const enableForceUpdate = args["--force"];
4447
const invalidItemMessages = targetItems.reduce((acc, item) => {
4548
const frontmatterErrors = checkFrontmatterType(item);
4649
if (frontmatterErrors.length > 0)
@@ -50,6 +53,16 @@ export const publish = async (argv: string[]) => {
5053
if (validationErrors.length > 0)
5154
return [...acc, { name: item.name, errors: validationErrors }];
5255

56+
if (!enableForceUpdate && item.isOlderThanRemote) {
57+
return [
58+
...acc,
59+
{
60+
name: item.name,
61+
errors: ["内容がQiita上の記事より古い可能性があります"],
62+
},
63+
];
64+
}
65+
5366
return acc;
5467
}, [] as { name: string; errors: string[] }[]);
5568
if (invalidItemMessages.length > 0) {

src/commands/pull.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ export const pull = async (argv: string[]) => {
1717

1818
const qiitaApi = await getQiitaApiInstance();
1919
const fileSystemRepo = await getFileSystemRepo();
20-
const isLocalUpdate = args["--force"];
20+
const forceUpdate = args["--force"];
2121

22-
await syncArticlesFromQiita({ fileSystemRepo, qiitaApi, isLocalUpdate });
22+
await syncArticlesFromQiita({ fileSystemRepo, qiitaApi, forceUpdate });
2323
console.log("Sync local articles from Qiita");
2424
console.log("Successful!");
2525
};

src/lib/entities/qiita-item.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export class QiitaItem {
88
public readonly rawBody: string;
99
public readonly name: string;
1010
public readonly modified: boolean;
11+
public readonly isOlderThanRemote: boolean;
1112
public readonly itemsShowPath: string;
1213
public readonly published: boolean;
1314
public readonly itemPath: string;
@@ -23,6 +24,7 @@ export class QiitaItem {
2324
rawBody,
2425
name,
2526
modified,
27+
isOlderThanRemote,
2628
itemsShowPath,
2729
published,
2830
itemPath,
@@ -37,6 +39,7 @@ export class QiitaItem {
3739
rawBody: string;
3840
name: string;
3941
modified: boolean;
42+
isOlderThanRemote: boolean;
4043
itemsShowPath: string;
4144
published: boolean;
4245
itemPath: string;
@@ -51,6 +54,7 @@ export class QiitaItem {
5154
this.rawBody = rawBody;
5255
this.name = name;
5356
this.modified = modified;
57+
this.isOlderThanRemote = isOlderThanRemote;
5458
this.itemsShowPath = itemsShowPath;
5559
this.published = published;
5660
this.itemPath = itemPath;

src/lib/file-system-repo.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ class FileContent {
135135
);
136136
}
137137

138+
isOlderThan(otherFileContent: FileContent | null): boolean {
139+
if (!otherFileContent) return false;
140+
const updatedAt = new Date(this.updatedAt);
141+
const otherUpdatedAt = new Date(otherFileContent.updatedAt);
142+
143+
return updatedAt < otherUpdatedAt;
144+
}
145+
138146
clone({ id }: { id: string }): FileContent {
139147
return new FileContent({
140148
title: this.title,
@@ -264,7 +272,7 @@ export class FileSystemRepo {
264272
private async syncItem(
265273
item: Item,
266274
beforeSync: boolean = false,
267-
isLocalUpdate: boolean = false
275+
forceUpdate: boolean = false
268276
) {
269277
const fileContent = FileContent.fromItem(item);
270278

@@ -279,17 +287,17 @@ export class FileSystemRepo {
279287
true
280288
);
281289

282-
if (data === null || remoteFileContent?.equals(data) || isLocalUpdate) {
290+
if (data === null || remoteFileContent?.equals(data) || forceUpdate) {
283291
await this.setItemData(fileContent, true);
284292
await this.setItemData(fileContent, false, basename);
285293
} else {
286294
await this.setItemData(fileContent, true);
287295
}
288296
}
289297

290-
async saveItems(items: Item[], isLocalUpdate: boolean = false) {
298+
async saveItems(items: Item[], forceUpdate: boolean = false) {
291299
const promises = items.map(async (item) => {
292-
await this.syncItem(item, false, isLocalUpdate);
300+
await this.syncItem(item, false, forceUpdate);
293301
});
294302

295303
await Promise.all(promises);
@@ -298,9 +306,9 @@ export class FileSystemRepo {
298306
async saveItem(
299307
item: Item,
300308
beforeSync: boolean = false,
301-
isLocalUpdate: boolean = false
309+
forceUpdate: boolean = false
302310
) {
303-
await this.syncItem(item, beforeSync, isLocalUpdate);
311+
await this.syncItem(item, beforeSync, forceUpdate);
304312
}
305313

306314
async loadItems(): Promise<QiitaItem[]> {
@@ -353,6 +361,7 @@ export class FileSystemRepo {
353361
slide: localFileContent.slide,
354362
name: basename,
355363
modified: !localFileContent.equals(remoteFileContent),
364+
isOlderThanRemote: localFileContent.isOlderThan(remoteFileContent),
356365
itemsShowPath: this.generateItemsShowPath(localFileContent.id, basename),
357366
published: remoteFileContent !== null,
358367
itemPath,
@@ -388,6 +397,7 @@ export class FileSystemRepo {
388397
slide: localFileContent.slide,
389398
name: basename,
390399
modified: !localFileContent.equals(remoteFileContent),
400+
isOlderThanRemote: localFileContent.isOlderThan(remoteFileContent),
391401
itemsShowPath: this.generateItemsShowPath(localFileContent.id, basename),
392402
published: remoteFileContent !== null,
393403
itemPath,

src/lib/sync-articles-from-qiita.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { config } from "./config";
55
export const syncArticlesFromQiita = async ({
66
fileSystemRepo,
77
qiitaApi,
8-
isLocalUpdate = false,
8+
forceUpdate = false,
99
}: {
1010
fileSystemRepo: FileSystemRepo;
1111
qiitaApi: QiitaApi;
12-
isLocalUpdate?: boolean;
12+
forceUpdate?: boolean;
1313
}) => {
1414
const per = 100;
1515
const userConfig = await config.getUserConfig();
@@ -22,6 +22,6 @@ export const syncArticlesFromQiita = async ({
2222
const result = userConfig.includePrivate
2323
? items
2424
: items.filter((item) => !item.private);
25-
await fileSystemRepo.saveItems(result, isLocalUpdate);
25+
await fileSystemRepo.saveItems(result, forceUpdate);
2626
}
2727
};

src/lib/view-models/items.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type ItemsIndexViewModel = {
1515

1616
export type ItemsShowViewModel = {
1717
error_messages: string[];
18+
is_older_than_remote: boolean;
1819
item_path: string;
1920
modified: boolean;
2021
organization_url_name: string | null;

src/server/api/items.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const itemsShow = async (req: Express.Request, res: Express.Response) => {
9595

9696
const result: ItemsShowViewModel = {
9797
error_messages: errorMessages,
98+
is_older_than_remote: item.isOlderThanRemote,
9899
item_path: itemPath,
99100
modified,
100101
organization_url_name: item.organizationUrlName,

0 commit comments

Comments
 (0)