implement phase 5 และ fix bugs

This commit is contained in:
Suphonchai Phoonsawat 2026-08-19 23:06:57 +07:00
parent f4b1c3e14b
commit 1bb850c6b8
40 changed files with 1507 additions and 429 deletions

View file

@ -1,45 +1,430 @@
import * as React from "react";
import Header from "./Header";
import HeroList, { HeroListItem } from "./HeroList";
import TextInsertion from "./TextInsertion";
import { makeStyles } from "@fluentui/react-components";
import { Ribbon24Regular, LockOpen24Regular, DesignIdeas24Regular } from "@fluentui/react-icons";
import { insertText } from "../taskpane";
/* global Word, Office */
interface AppProps {
title: string;
}
import * as React from "react";
import {
Button,
MessageBar,
MessageBarBody,
MessageBarActions,
Spinner,
Text,
makeStyles,
tokens,
} from "@fluentui/react-components";
import { DocumentArrowUp24Regular, Save24Regular, SignOut24Regular } from "@fluentui/react-icons";
import * as authService from "../../services/authService";
import { AuthRequiredError } from "../../services/apiClient";
import { getErrorMessage } from "../../services/errors";
import * as metadataService from "../../services/metadataService";
import { downloadVersion, getDownloadLink, getVersions } from "../../services/versioningService";
import { saveAndVersion, startVersioning } from "../../services/versionFlow";
import { VersionRecord } from "../../models/versionRecord";
import ConfirmVersionDialog from "./ConfirmVersionDialog";
import VersionHistoryPanel from "./VersionHistoryPanel";
/**
* Task pane หลัก — state machine ตาม Phase 5:
* checking → (ไม่มี token) signed-out
* → (มี token + ไม่ใช่ tracked document) untracked
* → (มี token + tracked document) tracking
*
* กติกา: ทุก async ต้อง catch — AuthRequiredError → กลับ signed-out อย่างเนียน (AC-9)
* error อื่นแสดง MessageBar ไม่ปล่อย unhandled rejection
*/
type Phase = "checking" | "signed-out" | "untracked" | "tracking";
/** FR-3 — ถามเอกสารทุก 30 วินาทีว่ามีแก้ไขที่ยังไม่ได้ save ไหม */
const UNSAVED_CHECK_INTERVAL_MS = 30_000;
const useStyles = makeStyles({
root: {
display: "flex",
flexDirection: "column",
minHeight: "100vh",
padding: tokens.spacingVerticalM,
gap: tokens.spacingVerticalS,
},
header: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
paddingBottom: tokens.spacingVerticalS,
borderBottom: `1px solid ${tokens.colorNeutralStroke1}`,
},
section: {
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
gap: tokens.spacingVerticalS,
paddingTop: tokens.spacingVerticalM,
},
checking: {
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: tokens.spacingVerticalM,
paddingTop: tokens.spacingVerticalXL,
},
});
const App: React.FC<AppProps> = (props: AppProps) => {
const App: React.FC = () => {
const styles = useStyles();
// The list items are static and won't change at runtime,
// so this should be an ordinary const, not a part of state.
const listItems: HeroListItem[] = [
{
icon: <Ribbon24Regular />,
primaryText: "Achieve more with Office integration",
const [phase, setPhase] = React.useState<Phase>("checking");
const [bootError, setBootError] = React.useState<string | null>(null);
const [username, setUsername] = React.useState<string | null>(null);
const [documentId, setDocumentId] = React.useState<string | null>(null);
const [versions, setVersions] = React.useState<VersionRecord[]>([]);
const [historyLoading, setHistoryLoading] = React.useState(false);
const [historyError, setHistoryError] = React.useState<string | null>(null);
const [signingIn, setSigningIn] = React.useState(false);
const [loginError, setLoginError] = React.useState<string | null>(null);
const [starting, setStarting] = React.useState(false);
const [saving, setSaving] = React.useState(false);
const [progressMessage, setProgressMessage] = React.useState<string | null>(null);
const [confirmOpen, setConfirmOpen] = React.useState(false);
const [flowError, setFlowError] = React.useState<string | null>(null);
const [flowSuccess, setFlowSuccess] = React.useState<string | null>(null);
const [downloadingVersionId, setDownloadingVersionId] = React.useState<string | null>(null);
const [hasUnsavedChanges, setHasUnsavedChanges] = React.useState(false);
/** 401 ที่ refresh ไม่ช่วย → กลับหน้า sign-in อย่างเนียน (AC-9) */
const handleAuthRequired = React.useCallback((error: unknown): boolean => {
if (error instanceof AuthRequiredError) {
setPhase("signed-out");
setDocumentId(null);
setVersions([]);
setLoginError("เซสชันหมดอายุ — กรุณา login ใหม่");
return true;
}
return false;
}, []);
const refreshHistory = React.useCallback(
async (id: string) => {
setHistoryLoading(true);
setHistoryError(null);
try {
setVersions(await getVersions(id));
} catch (error) {
if (!handleAuthRequired(error)) {
setHistoryError(getErrorMessage(error, "โหลด version history ไม่สำเร็จ"));
}
} finally {
setHistoryLoading(false);
}
},
{
icon: <LockOpen24Regular />,
primaryText: "Unlock features and functionality",
},
{
icon: <DesignIdeas24Regular />,
primaryText: "Create and visualize like a pro",
},
];
[handleAuthRequired]
);
/** ตรวจตอนเปิด task pane (FR-1): token → SavedByAddIn → history */
const detect = React.useCallback(async () => {
setPhase("checking");
setBootError(null);
try {
const token = await authService.getAccessToken();
if (!token) {
setPhase("signed-out");
return;
}
authService.startAutoRefresh();
setUsername(authService.getUsername() ?? null);
const tracked = await metadataService.isTrackedDocument();
if (!tracked) {
setPhase("untracked");
return;
}
const properties = await metadataService.readTrackedProperties();
const id = properties.documentTrackingId;
if (!id) {
// mark ว่า track แต่ไม่มี id (ไฟล์เสียหาย) — ให้เริ่มใหม่แบบ untracked
setPhase("untracked");
return;
}
setDocumentId(id);
setPhase("tracking");
await refreshHistory(id);
} catch (error) {
setBootError(getErrorMessage(error, "ตรวจสอบเอกสารไม่สำเร็จ"));
}
}, [refreshHistory]);
React.useEffect(() => {
void detect();
}, [detect]);
/** FR-3 — poll `document.saved` เฉพาะตอน tracking */
React.useEffect(() => {
if (phase !== "tracking") {
setHasUnsavedChanges(false);
return;
}
const interval = setInterval(() => {
void (async () => {
try {
const unsaved = await Word.run(async (context) => {
const document = context.document;
document.load("saved");
await context.sync();
return !document.saved;
});
setHasUnsavedChanges(unsaved);
} catch {
// อ่านไม่ได้รอบนี้ก็ข้าม — banner เป็นแค่ soft reminder
}
})();
}, UNSAVED_CHECK_INTERVAL_MS);
return () => clearInterval(interval);
}, [phase]);
const handleSignIn = async () => {
setSigningIn(true);
setLoginError(null);
try {
const result = await authService.login();
if (result.ok) {
await detect();
} else {
setLoginError(result.error);
}
} catch (error) {
setLoginError(getErrorMessage(error, "เข้าสู่ระบบไม่สำเร็จ"));
} finally {
setSigningIn(false);
}
};
const handleSignOut = () => {
authService.signOut();
setUsername(null);
setDocumentId(null);
setVersions([]);
setLoginError(null);
setPhase("signed-out");
};
/** Start Versioning — stamp รอบแรก + upload v1 (FR-1/AC-5) */
const handleStartVersioning = async () => {
setStarting(true);
setFlowError(null);
setFlowSuccess(null);
try {
const result = await startVersioning(setProgressMessage);
setDocumentId(result.documentId);
setPhase("tracking");
setFlowSuccess(`เริ่มติดตามเอกสารนี้แล้ว — สร้างเป็น version 1`);
await refreshHistory(result.documentId);
} catch (error) {
if (!handleAuthRequired(error)) {
setFlowError(getErrorMessage(error, "เริ่มติดตามเอกสารไม่สำเร็จ"));
}
} finally {
setProgressMessage(null);
setStarting(false);
}
};
/** Save & Version (FR-2) — ตามลำดับ spec: confirm → stamp → XML → save → file+fields → upload */
const handleSaveAndVersion = async () => {
if (!documentId) {
return;
}
setSaving(true);
setFlowError(null);
setFlowSuccess(null);
try {
const latestVersionNumber = versions[0]?.versionNumber ?? 0;
const result = await saveAndVersion(documentId, latestVersionNumber, setProgressMessage);
setFlowSuccess(`สร้าง version ${result.versionNumber} เรียบร้อย`);
await refreshHistory(documentId);
} catch (error) {
if (!handleAuthRequired(error)) {
setFlowError(getErrorMessage(error, "สร้าง version ไม่สำเร็จ"));
}
} finally {
setProgressMessage(null);
setSaving(false);
}
};
/**
* FR-4 — ดาวน์โหลด version ที่เลือก: ขอ presigned URL อายุสั้นจาก backend แล้วเปิดใน
* เบราว์เซอร์ของเครื่อง (webview ของ Word — โดยเฉพาะ WKWebView บน Mac — ทิ้ง blob download
* แบบเงียบๆ ไฟล์ไม่เคยถูกเขียนที่ไหนเลย) · host ไหนไม่มี openBrowserWindow จะย้อนกลับไปใช้ blob
*/
const handleDownload = async (version: VersionRecord) => {
if (!documentId) {
return;
}
setDownloadingVersionId(version.versionId);
setFlowError(null);
try {
const link = await getDownloadLink(documentId, version.versionId);
if (typeof Office.context.ui.openBrowserWindow === "function") {
Office.context.ui.openBrowserWindow(link.url);
setFlowSuccess(`กำลังดาวน์โหลด v${version.versionNumber} ผ่านเบราว์เซอร์ของคุณ — ดูไฟล์ที่โฟลเดอร์ Downloads`);
} else {
const blob = await downloadVersion(documentId, version.versionId);
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = `document_v${version.versionNumber}.docx`;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
}
} catch (error) {
if (!handleAuthRequired(error)) {
setFlowError(getErrorMessage(error, "ดาวน์โหลด version ไม่สำเร็จ"));
}
} finally {
setDownloadingVersionId(null);
}
};
return (
<div className={styles.root}>
<Header logo="assets/logo-filled.png" title={props.title} message="Welcome" />
<HeroList message="Discover what this add-in can do for you today!" items={listItems} />
<TextInsertion insertText={insertText} />
<div className={styles.header}>
<Text weight="semibold">e-Court Documents Versioning</Text>
{phase !== "signed-out" && phase !== "checking" && username ? (
<Button
size="small"
appearance="subtle"
icon={<SignOut24Regular />}
onClick={handleSignOut}
>
{username} · ออกจากระบบ
</Button>
) : null}
</div>
{phase === "checking" ? (
<div className={styles.checking}>
<Spinner label="กำลังตรวจสอบเอกสาร…" />
{bootError ? (
<MessageBar intent="error">
<MessageBarBody>{bootError}</MessageBarBody>
<MessageBarActions>
<Button size="small" appearance="outline" onClick={() => void detect()}>
ลองใหม่
</Button>
</MessageBarActions>
</MessageBar>
) : null}
</div>
) : null}
{phase === "signed-out" ? (
<div className={styles.section}>
<Text size={300}>เข้าสู่ระบบเพื่อเริ่มใช้งาน versioning</Text>
<Text size={200} style={{ color: tokens.colorNeutralForeground3 }}>
ใช้บัญชีของ realm <code>word-versioning</code> — หน้าต่าง login จะเปิดแยกต่างหาก
</Text>
<Button appearance="primary" disabled={signingIn} onClick={() => void handleSignIn()}>
{signingIn ? "กำลังเข้าสู่ระบบ…" : "Sign in"}
</Button>
{loginError ? (
<MessageBar intent="warning">
<MessageBarBody>{loginError}</MessageBarBody>
</MessageBar>
) : null}
</div>
) : null}
{phase === "untracked" ? (
<div className={styles.section}>
<Text size={300}>เอกสารนี้ยังไม่ได้ติดตาม version</Text>
<Text size={200} style={{ color: tokens.colorNeutralForeground3 }}>
กดปุ่มด้านล่างเพื่อเริ่ม track — ระบบจะ stamp metadata ลงไฟล์และอัปโหลดเป็น version 1
(ไฟล์ที่สร้างจากเว็บ portal จะถูกตรวจพบและข้ามขั้นตอนนี้อัตโนมัติ)
</Text>
<Button
appearance="primary"
icon={<DocumentArrowUp24Regular />}
disabled={starting}
onClick={() => void handleStartVersioning()}
>
{starting ? "กำลังเริ่มติดตาม…" : "Start Versioning This Document"}
</Button>
{progressMessage ? (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Spinner size="tiny" />
<Text size={200}>{progressMessage}</Text>
</div>
) : null}
{flowError ? (
<MessageBar intent="error">
<MessageBarBody>{flowError}</MessageBarBody>
</MessageBar>
) : null}
</div>
) : null}
{phase === "tracking" ? (
<div className={styles.section}>
{hasUnsavedChanges ? (
<MessageBar intent="warning">
<MessageBarBody>
มีการแก้ไขที่ยังไม่ได้ version — กด Save &amp; Version เพื่อบันทึกเป็น version ใหม่
</MessageBarBody>
</MessageBar>
) : null}
{flowSuccess ? (
<MessageBar intent="success">
<MessageBarBody>{flowSuccess}</MessageBarBody>
</MessageBar>
) : null}
{flowError ? (
<MessageBar intent="error">
<MessageBarBody>{flowError}</MessageBarBody>
</MessageBar>
) : null}
<Button
appearance="primary"
size="large"
icon={<Save24Regular />}
disabled={saving}
onClick={() => setConfirmOpen(true)}
>
Save &amp; Version
</Button>
{saving && progressMessage ? (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Spinner size="tiny" />
<Text size={200}>{progressMessage}</Text>
</div>
) : null}
<Text size={200} style={{ color: tokens.colorNeutralForeground3 }}>
tracking id: {documentId?.slice(0, 8)}…
</Text>
<VersionHistoryPanel
versions={versions}
loading={historyLoading}
error={historyError}
downloadingVersionId={downloadingVersionId}
onDownload={(version) => void handleDownload(version)}
onRetry={() => documentId && void refreshHistory(documentId)}
/>
</div>
) : null}
<ConfirmVersionDialog
open={confirmOpen}
busy={saving}
onConfirm={() => void handleSaveAndVersion()}
onDismiss={() => setConfirmOpen(false)}
/>
</div>
);
};

View file

@ -0,0 +1,55 @@
import * as React from "react";
import {
Button,
Dialog,
DialogActions,
DialogBody,
DialogContent,
DialogSurface,
DialogTitle,
Spinner,
} from "@fluentui/react-components";
interface ConfirmVersionDialogProps {
open: boolean;
/** กำลังอยู่ระหว่าง flow หลังกดยืนยันแล้ว — ปุ่นทั้งสอง disable + แสดง spinner */
busy: boolean;
onConfirm: () => void;
onDismiss: () => void;
}
/** FR-2 ขั้นตอนที่ 1 — dialog ยืนยัน "สร้าง version ใหม่ของเอกสารนี้หรือไม่?" */
export const ConfirmVersionDialog: React.FC<ConfirmVersionDialogProps> = ({
open,
busy,
onConfirm,
onDismiss,
}) => {
return (
<Dialog open={open} onOpenChange={(_event, data) => !data.open && !busy && onDismiss()} modalType="alert">
<DialogSurface>
<DialogBody>
<DialogTitle>สร้าง version ใหม่?</DialogTitle>
<DialogContent>
ระบบจะบันทึกไฟล์ปัจจุบันและอัปโหลดเป็น version ถัดไปของเอกสารนี้
{busy ? (
<div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 12 }}>
<Spinner size="tiny" /> กำลังดำเนินการ…
</div>
) : null}
</DialogContent>
<DialogActions>
<Button appearance="secondary" disabled={busy} onClick={onDismiss}>
ยกเลิก
</Button>
<Button appearance="primary" disabled={busy} onClick={onConfirm}>
สร้าง version
</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
);
};
export default ConfirmVersionDialog;

View file

@ -1,38 +0,0 @@
import * as React from "react";
import { Image, tokens, makeStyles } from "@fluentui/react-components";
export interface HeaderProps {
title: string;
logo: string;
message: string;
}
const useStyles = makeStyles({
welcome__header: {
display: "flex",
flexDirection: "column",
alignItems: "center",
paddingBottom: "30px",
paddingTop: "100px",
backgroundColor: tokens.colorNeutralBackground3,
},
message: {
fontSize: tokens.fontSizeHero900,
fontWeight: tokens.fontWeightRegular,
fontColor: tokens.colorNeutralBackgroundStatic,
},
});
const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
const { title, logo, message } = props;
const styles = useStyles();
return (
<section className={styles.welcome__header}>
<Image width="90" height="90" src={logo} alt={title} />
<h1 className={styles.message}>{message}</h1>
</section>
);
};
export default Header;

View file

@ -1,62 +0,0 @@
import * as React from "react";
import { tokens, makeStyles } from "@fluentui/react-components";
export interface HeroListItem {
icon: React.JSX.Element;
primaryText: string;
}
export interface HeroListProps {
message: string;
items: HeroListItem[];
}
const useStyles = makeStyles({
list: {
marginTop: "20px",
},
listItem: {
paddingBottom: "20px",
display: "flex",
},
icon: {
marginRight: "10px",
},
itemText: {
fontSize: tokens.fontSizeBase300,
fontColor: tokens.colorNeutralBackgroundStatic,
},
welcome__main: {
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
},
message: {
fontSize: tokens.fontSizeBase500,
fontColor: tokens.colorNeutralBackgroundStatic,
fontWeight: tokens.fontWeightRegular,
paddingLeft: "10px",
paddingRight: "10px",
},
});
const HeroList: React.FC<HeroListProps> = (props: HeroListProps) => {
const { items, message } = props;
const styles = useStyles();
const listItems = items.map((item, index) => (
<li className={styles.listItem} key={index}>
<i className={styles.icon}>{item.icon}</i>
<span className={styles.itemText}>{item.primaryText}</span>
</li>
));
return (
<div className={styles.welcome__main}>
<h2 className={styles.message}>{message}</h2>
<ul className={styles.list}>{listItems}</ul>
</div>
);
};
export default HeroList;

View file

@ -1,57 +0,0 @@
import * as React from "react";
import { useState } from "react";
import { Button, Field, Textarea, tokens, makeStyles } from "@fluentui/react-components";
/* global HTMLTextAreaElement */
interface TextInsertionProps {
insertText: (_text: string) => void;
}
const useStyles = makeStyles({
instructions: {
fontWeight: tokens.fontWeightSemibold,
marginTop: "20px",
marginBottom: "10px",
},
textPromptAndInsertion: {
display: "flex",
flexDirection: "column",
alignItems: "center",
},
textAreaField: {
marginLeft: "20px",
marginTop: "30px",
marginBottom: "20px",
marginRight: "20px",
maxWidth: "50%",
},
});
const TextInsertion: React.FC<TextInsertionProps> = (props: TextInsertionProps) => {
const [text, setText] = useState<string>("Some text.");
const handleTextInsertion = async () => {
await props.insertText(text);
};
const handleTextChange = async (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setText(event.target.value);
};
const styles = useStyles();
return (
<div className={styles.textPromptAndInsertion}>
<Field className={styles.textAreaField} size="large" label="Enter text to be inserted into the document.">
<Textarea size="large" value={text} onChange={handleTextChange} />
</Field>
<Field className={styles.instructions}>Click the button to insert text.</Field>
<Button appearance="primary" disabled={false} size="large" onClick={handleTextInsertion}>
Insert text
</Button>
</div>
);
};
export default TextInsertion;

View file

@ -0,0 +1,128 @@
import * as React from "react";
import {
Badge,
Button,
MessageBar,
MessageBarBody,
Spinner,
Text,
makeStyles,
tokens,
} from "@fluentui/react-components";
import { ArrowDownload24Regular, History24Regular } from "@fluentui/react-icons";
import { VersionRecord } from "../../models/versionRecord";
interface VersionHistoryPanelProps {
versions: VersionRecord[];
loading: boolean;
error: string | null;
/** versionId ของ version ที่กำลังดาวน์โหลดอยู่ (แสดง spinner ที่ปุ่ม) */
downloadingVersionId: string | null;
onDownload: (version: VersionRecord) => void;
onRetry: () => void;
}
const useStyles = makeStyles({
root: {
display: "flex",
flexDirection: "column",
gap: tokens.spacingVerticalS,
},
header: {
display: "flex",
alignItems: "center",
gap: tokens.spacingHorizontalXS,
marginTop: tokens.spacingVerticalL,
marginBottom: tokens.spacingVerticalXXS,
},
item: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: tokens.spacingHorizontalS,
padding: `${tokens.spacingVerticalXS} ${tokens.spacingHorizontalS}`,
borderRadius: tokens.borderRadiusMedium,
":hover": {
backgroundColor: tokens.colorNeutralBackground1Hover,
},
},
itemInfo: {
display: "flex",
flexDirection: "column",
minWidth: 0,
},
metaRow: {
color: tokens.colorNeutralForeground3,
fontSize: tokens.fontSizeBase200,
},
});
function formatTimestamp(iso: string): string {
const date = new Date(iso);
return Number.isNaN(date.getTime())
? iso
: date.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
}
/** FR-4 — แสดง version history (เลข version / เวลา / ผู้สร้าง) + ปุ่มดาวน์โหลด version นั้น */
export const VersionHistoryPanel: React.FC<VersionHistoryPanelProps> = ({
versions,
loading,
error,
downloadingVersionId,
onDownload,
onRetry,
}) => {
const styles = useStyles();
return (
<div className={styles.root}>
<div className={styles.header}>
<History24Regular />
<Text weight="semibold">Version history</Text>
</div>
{loading ? <Spinner size="small" label="กำลังโหลด…" /> : null}
{error ? (
<MessageBar intent="error">
<MessageBarBody>
{error}{" "}
<Button size="small" appearance="subtle" onClick={onRetry}>
ลองใหม่
</Button>
</MessageBarBody>
</MessageBar>
) : null}
{!loading && !error && versions.length === 0 ? (
<Text size={200} style={{ color: tokens.colorNeutralForeground3 }}>
ยังไม่มี version ในระบบ
</Text>
) : null}
{versions.map((version) => (
<div className={styles.item} key={version.versionId}>
<div className={styles.itemInfo}>
<Badge appearance="filled" color="brand">
v{version.versionNumber}
</Badge>
<span className={styles.metaRow}>{formatTimestamp(version.createdAt)}</span>
<span className={styles.metaRow}>โดย {version.createdByUserId}</span>
</div>
<Button
icon={<ArrowDownload24Regular />}
size="small"
appearance="subtle"
disabled={downloadingVersionId !== null}
onClick={() => onDownload(version)}
>
{downloadingVersionId === version.versionId ? "กำลังดาวน์โหลด…" : "ดาวน์โหลด"}
</Button>
</div>
))}
</div>
);
};
export default VersionHistoryPanel;

View file

@ -5,8 +5,6 @@ import { FluentProvider, webLightTheme } from "@fluentui/react-components";
/* global document, Office, module, require, HTMLElement */
const title = "Contoso Task Pane Add-in";
const rootElement: HTMLElement | null = document.getElementById("container");
const root = rootElement ? createRoot(rootElement) : undefined;
@ -14,7 +12,7 @@ const root = rootElement ? createRoot(rootElement) : undefined;
Office.onReady(() => {
root?.render(
<FluentProvider theme={webLightTheme}>
<App title={title} />
<App />
</FluentProvider>
);
});

View file

@ -8,7 +8,7 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Contoso Task Pane Add-in</title>
<title>e-Court Documents Versioning</title>
<!-- Office JavaScript API -->
<script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>

View file

@ -1,14 +0,0 @@
/* global Word console */
export async function insertText(text: string) {
// Write text to the document.
try {
await Word.run(async (context) => {
let body = context.document.body;
body.insertParagraph(text, Word.InsertLocation.end);
await context.sync();
});
} catch (error) {
console.log("Error: " + error);
}
}