UNPKG

@kwiz/common

Version:

KWIZ common utilities and helpers for M365 platform

686 lines (592 loc) 30.2 kB
import { GetError } from "../../exports-index"; import { jsonStringify } from "../../helpers/json"; import { isNotEmptyArray, isNotEmptyString, isNullOrEmptyString, isNullOrUndefined, isNumber, isNumeric, newGuid } from "../../helpers/typecheckers"; import { encodeURIComponentEX, makeServerRelativeUrl, normalizeUrl } from "../../helpers/url"; import { IDictionary } from "../../types/common.types"; import { IRequestBody, IRestOptions, IRestResponseType, jsonTypes } from "../../types/rest.types"; import { IFolderBasicInfo, IFolderInfo } from "../../types/sharepoint.types"; import { FileLevel, IFileInfoWithModerationStatus, ModerationStatus } from "../../types/sharepoint.utils.types"; import { ConsoleLogger } from "../consolelogger"; import { GetJson, GetJsonSync, longLocalCache, mediumLocalCache, noLocalCache, shortLocalCache } from "../rest"; import { GetRestBaseUrl, GetSiteUrl } from "./common"; import { GetListRestUrl } from "./list"; const logger = ConsoleLogger.get("SharePoint.Rest.FileNFolder"); let existingFolders: string[] = []; export async function EnsureFolderPath(siteUrl: string, folderServerRelativeUrl: string): Promise<boolean> { siteUrl = GetSiteUrl(siteUrl); //issue 7176 folderServerRelativeUrl = makeServerRelativeUrl(folderServerRelativeUrl, siteUrl); if (existingFolders.indexOf(folderServerRelativeUrl) >= 0) { return true; } let url = `${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderServerRelativeUrl}')?$select=exists`; let folder = await GetJson<{ d: { Exists: boolean; }; }>(url); if (folder && folder.d.Exists) { existingFolders.push(folderServerRelativeUrl); return true; } else { let parts = folderServerRelativeUrl.split('/'); if (parts.length > 1) { let parentFolder = parts.slice(0, parts.length - 1).join('/'); //ensure parent let parent = await EnsureFolderPath(siteUrl, parentFolder); if (parent) { //create it let ensure = await EnsureFolder(siteUrl, parentFolder, parts[parts.length - 1]); if (ensure.Exists) { existingFolders.push(folderServerRelativeUrl); return true; } } } } return false; } export function EnsureFolder(siteUrl: string, parentFolderServerRelativeUrl: string, folderName: string): Promise<{ Exists: boolean; ServerRelativeUrl?: string; }> { siteUrl = GetSiteUrl(siteUrl); parentFolderServerRelativeUrl = makeServerRelativeUrl(parentFolderServerRelativeUrl, siteUrl); return GetJson<{ d: { Exists: boolean; ServerRelativeUrl: string; }; }>(`${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${parentFolderServerRelativeUrl}')/folders/add(url='${folderName}')`, null, { method: "POST", spWebUrl: siteUrl }) .then(r => { return r.d; }) .catch<{ Exists: boolean; ServerRelativeUrl?: string; }>(() => { return { Exists: false }; }); } export function DeleteFolder(siteUrl: string, folderUrl: string): Promise<boolean> { siteUrl = GetSiteUrl(siteUrl); folderUrl = makeServerRelativeUrl(folderUrl, siteUrl); var requestUrl = `${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderUrl}')`; return GetJson(requestUrl, null, { method: "POST", xHttpMethod: "DELETE" }) .then(r => true) .catch<boolean>((e) => false); } export function GetFolderFiles(siteUrl: string, folderUrl: string): Promise<IFileInfoWithModerationStatus[]> { siteUrl = GetSiteUrl(siteUrl); folderUrl = makeServerRelativeUrl(folderUrl, siteUrl); var requestUrl = `${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderUrl}')` + `/files?$select=Level,Exists,Name,ServerRelativeUrl,Title,TimeCreated,TimeLastModified,ListItemAllFields/OData__ModerationStatus&$expand=ListItemAllFields`; return GetJson<{ d: { results: IFileInfoWithModerationStatus[]; }; }>(requestUrl).then(r => { return r.d && r.d.results || []; }).catch<IFileInfoWithModerationStatus[]>(() => { return []; }); } export function UploadFileSync(siteUrl: string, folderServerRelativeUrl: string, fileName: string, fileContent: IRequestBody): { Exists: boolean; ServerRelativeUrl?: string; ListItemAllFields?: { [fieldInternalName: string]: any; }; } { siteUrl = GetSiteUrl(siteUrl); folderServerRelativeUrl = makeServerRelativeUrl(folderServerRelativeUrl, siteUrl); let res = GetJsonSync<{ d: { Exists: boolean; ServerRelativeUrl: string; }; }>( `${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderServerRelativeUrl}')/files/add(url='${fileName}',overwrite=true)?$expand=ListItemAllFields`, fileContent, { method: 'POST', spWebUrl: siteUrl }); return res.success && res.result && res.result.d ? res.result.d : { Exists: false }; } export async function UploadFile(siteUrl: string, folderServerRelativeUrl: string, fileName: string, fileContent: IRequestBody, /** default options: { overwrite: true } */ options?: { overwrite?: boolean; /** set to true to automatically find the next available file name. uploading file.ext to a folder that has that file will upload a file named file.1.ext instead */ autoRename?: boolean; }): Promise<{ Exists: boolean; ServerRelativeUrl?: string; [fieldInternalName: string]: any; }> { siteUrl = GetSiteUrl(siteUrl); options = options || { overwrite: true }; folderServerRelativeUrl = makeServerRelativeUrl(folderServerRelativeUrl, siteUrl); if (options && options.autoRename) { //get all files from this folder and find the next available name let files = await GetFolderFiles(siteUrl, folderServerRelativeUrl); let fileNames = files.map(f => f.Name.toLowerCase()); let counter = 0; let originalName = fileName.split('.'); originalName.splice(originalName.length - 1, 0, counter.toString()); while (fileNames.includes(fileName.toLowerCase())) { counter++; originalName[originalName.length - 2] = counter.toString(); fileName = originalName.join('.'); } } return GetJson<{ d: { Exists: boolean; ServerRelativeUrl: string; }; }>( `${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderServerRelativeUrl}')/files/add(url='${fileName}'${options.overwrite ? ',overwrite=true' : ''})?$expand=ListItemAllFields`, fileContent, { method: 'POST', spWebUrl: siteUrl, allowCache: false, postCacheKey: null })//Issue 6657 force set "POST" since we might send empty string as the value .then(r => { return r.d; }) .catch<{ Exists: boolean; ServerRelativeUrl?: string; [fieldInternalName: string]: any; }>(() => { return { Exists: false }; }); } export async function PublishFile(siteUrl: string, fileUrl: string, comment: string = "") { let result = await _moderateFile(siteUrl, fileUrl, "publish", comment); return result; } export async function UnpublishFile(siteUrl: string, fileUrl: string, comment: string = "") { let result = await _moderateFile(siteUrl, fileUrl, "unpublish", comment); return result; } export async function ApproveFile(siteUrl: string, fileUrl: string, comment: string = "") { siteUrl = GetSiteUrl(siteUrl); let result = await _moderateFile(siteUrl, fileUrl, "approve", comment); return result; } export async function RejectFile(siteUrl: string, fileUrl: string, comment: string = "") { let result = await _moderateFile(siteUrl, fileUrl, "deny", comment); return result; } async function _moderateFile(siteUrl: string, fileUrl: string, action: "publish" | "unpublish" | "approve" | "deny", comment: string = "") { siteUrl = GetSiteUrl(siteUrl); let fileServerRelativeUrl = makeServerRelativeUrl(fileUrl, siteUrl); try { let hasComments = !isNullOrEmptyString(comment); let publishUrl = `${GetRestBaseUrl(siteUrl)}/Web/getFileByServerRelativeUrl('${fileServerRelativeUrl}')/${action}${hasComments ? `(@a1)?@a1=%27${encodeURIComponentEX(comment, { singleQuoteMultiplier: 2 })}%27` : '()'}`; let publishResult = await GetJson<{ "odata.null": boolean }>(publishUrl, null, { method: "POST", jsonMetadata: jsonTypes.nometadata, includeDigestInPost: true }); return !isNullOrUndefined(publishResult) && publishResult["odata.null"] === true; } catch { } return false; } export function RecycleFile(siteUrl: string, fileServerRelativeUrl: string): Promise<boolean> { siteUrl = GetSiteUrl(siteUrl); let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl) + "/recycle()"; return GetJson(fileRestUrl, null, { method: "POST", headers: { "IF-MATCH": "*" } }) .then(r => true) .catch<boolean>((e) => false); } export function DeleteFile(siteUrl: string, fileServerRelativeUrl: string): Promise<boolean> { siteUrl = GetSiteUrl(siteUrl); let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); return GetJson(fileRestUrl, null, { method: "POST", xHttpMethod: "DELETE" }) .then(r => true) .catch<boolean>((e) => false); } /** get the REST url for the site/_api/web/getfile....() */ function GetFileRestUrl(siteUrl: string, fileServerRelativeUrl: string) { fileServerRelativeUrl = makeServerRelativeUrl(fileServerRelativeUrl, siteUrl); let fileRestUrl = `${GetRestBaseUrl(siteUrl)}/Web/getFileByServerRelativeUrl('${fileServerRelativeUrl}')`; return fileRestUrl; } export function GetFileSync<T>(siteUrl: string, fileServerRelativeUrl: string, responseType?: IRestResponseType, options?: { /** default, short cache. */ cache?: "long" | "short" | "nocache"; }): { Exists: boolean; Content?: T; } { siteUrl = GetSiteUrl(siteUrl); let restOptions: IRestOptions = isNullOrUndefined(options) || options.cache !== "long" ? { ...shortLocalCache } : { ...longLocalCache }; if (options && options.cache === "nocache") restOptions.forceCacheUpdate = true; if (!isNullOrUndefined(responseType)) { restOptions.responseType = responseType; } let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); if (!restOptions.forceCacheUpdate && reloadCacheFileModifiedRecently(siteUrl, fileServerRelativeUrl)) { restOptions.forceCacheUpdate = true; } let response = GetJsonSync<T>(`${fileRestUrl}/$value`, null, restOptions); if (response && response.success) return { Exists: true, Content: response.result }; else return { Exists: false }; } /** @deprecated use GetFileEx */ export function GetFile<T>(siteUrl: string, fileServerRelativeUrl: string, allowCache?: boolean, responseType?: IRestResponseType): Promise<{ Exists: boolean; Content?: T; }> { return GetFileEx(siteUrl, fileServerRelativeUrl, { allowCache, responseType }); } export async function GetFileEx<T>(siteUrl: string, fileServerRelativeUrl: string, options?: { allowCache?: boolean; responseType?: IRestResponseType; /** version #.# or version ID as number */ version?: string | number; }): Promise<{ Exists: boolean; Content?: T; }> { siteUrl = GetSiteUrl(siteUrl); options = options || {}; let restOptions: IRestOptions = { ...(options.allowCache === true ? shortLocalCache : noLocalCache), forceCacheUpdate: options.allowCache !== true }; if (!isNullOrUndefined(options.responseType)) { restOptions.responseType = options.responseType; } let version = options.version; let versionPart = ""; if (isNumber(version) && version > 0 || isNotEmptyString(version)) { //this end point does not work on MSAL claims // //get content of specific version // let fileSiteRelativeUrl = fileServerRelativeUrl.slice(siteUrl.length - 1); // let versionUrl = `${siteUrl}_vti_history/${FileVersionToVersionId(options.version)}${fileSiteRelativeUrl}`; // try { // restOptions.jsonMetadata = jsonTypes.nometadata; // let versionContent = await GetJson<T>(versionUrl, undefined, restOptions); // return { Exists: isString(versionContent), Content: versionContent }; // } catch (e) { // return { Exists: false }; // } versionPart = `/versions(${FileVersionToVersionId(options.version)})/`; } let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); if (!restOptions.forceCacheUpdate && reloadCacheFileModifiedRecently(siteUrl, fileServerRelativeUrl)) { restOptions.forceCacheUpdate = true; } return GetJson<T>(`${fileRestUrl}${versionPart}/$value`, null, restOptions).then(r => { return { Exists: true, Content: r }; }).catch<{ Exists: boolean; Content?: T; }>(() => { return { Exists: false }; }); } export interface iFileVersionInfo { CheckInComment: string; Created: Date; /** version ID: major*512 + minor */ ID: number; IsCurrentVersion: boolean; Length: string; Size: number; /** site relative _vti_history link. Better use /getFile..../versions(id)/$value */ Url: string; /** version format #.# */ VersionLabel: string; } /** get file version history olders version first with correct check in comment, does NOT include current version */ export async function GetFileVersionHistory(siteUrl: string, fileServerRelativeUrl: string, options?: { refreshCache?: boolean; }): Promise<iFileVersionInfo[]> { siteUrl = GetSiteUrl(siteUrl); options = options || {}; let restOptions: IRestOptions = { allowCache: options.refreshCache !== true, jsonMetadata: jsonTypes.nometadata }; let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); if (!restOptions.forceCacheUpdate && reloadCacheFileModifiedRecently(siteUrl, fileServerRelativeUrl)) { restOptions.forceCacheUpdate = true; } try { const result = await GetJson<{ value: iFileVersionInfo[] }>(`${fileRestUrl}/versions`, null, restOptions); //Created will come in as string result.value.forEach(v => v.Created = new Date(v.Created as any as string)); return result.value; } catch (e) { logger.error(GetError(e)); } } /** version: 1.5 >> version ID for history */ export function FileVersionToVersionId(version: string | number) { try { if (isNumber(version)) return version; const vSplit = version.split('.'); const major = parseInt(vSplit[0], 10); const minor = parseInt(vSplit[1], 10); let versionId = (major * 512) + minor; return versionId; } catch (e) { } return null; } var $reloadCacheFileModifiedRecentlyFlagged: string[] = []; function reloadCacheFileModifiedRecently(siteUrl: string, fileServerRelativeUrl: string) { let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); let key = fileRestUrl.toLowerCase(); //only flag it once, first time it is requested... if (!$reloadCacheFileModifiedRecentlyFlagged.includes(key)) { try { $reloadCacheFileModifiedRecentlyFlagged.push(key); let fileInfo = GetJsonSync<{ TimeLastModified: string; }>(`${fileRestUrl}?$select=TimeLastModified`, null, { allowCache: true,//only allow in-memory cache for this jsonMetadata: jsonTypes.nometadata }); if (fileInfo.success && fileInfo.result) { let modified = new Date(fileInfo.result.TimeLastModified); let now = new Date(); let difference = now.getTime() - modified.getTime(); if (difference < 5 * 60 * 1000) { //file has changed in the past 5 minutes - do not allow cache on it. //happens when user uses classic app to change settings, the clear cache does not clear it on the main //site URL ( support case - Issue 778 780 & 782 ) return true; } } } catch (e) { } } return false; } /** Get file size (bytes) by file server relative url - can also get this by selecting FileSizeDisplay field on the item */ export async function GetFileSize(siteUrl: string, fileServerRelativeUrl: string, allowCache?: boolean); /** Get file size (bytes) by list item - can also get this by selecting FileSizeDisplay field on the item */ export async function GetFileSize(siteUrl: string, listId: string, itemId: number, allowCache?: boolean); export async function GetFileSize(siteUrl: string, fileServerRelativeUrlOrListId: string, itemIdOrAllowCache?: number | boolean, allowCache?: boolean): Promise<number> { siteUrl = GetSiteUrl(siteUrl); let requestUrl = ""; if (isNumber(itemIdOrAllowCache) || isNumeric(itemIdOrAllowCache)) { requestUrl = GetListRestUrl(siteUrl, fileServerRelativeUrlOrListId) + `/items(${itemIdOrAllowCache})/File`; } else { allowCache = itemIdOrAllowCache === true; requestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrlOrListId); } let options: IRestOptions = { allowCache: allowCache === true, jsonMetadata: jsonTypes.nometadata }; try { let result = await GetJson<{ vti_x005f_filesize: number; }>(`${requestUrl}/Properties?$select=vti_x005f_filesize`, null, options); return result.vti_x005f_filesize; } catch (e) { return null; } } export async function GetListFolders(siteUrl: string, listIdOrTitle: string): Promise<IFolderBasicInfo[]> { siteUrl = GetSiteUrl(siteUrl); //switched to get request with no meta data - much faster. let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items?$Select=Folder/ServerRelativeUrl,Folder/Name&$filter=FSObjType eq 1&$expand=Folder`; let results: IFolderBasicInfo[] = []; try { let requestResult = (await GetJson<{ value: { Folder: IFolderBasicInfo; }[]; }>(url, null, { allowCache: true, jsonMetadata: jsonTypes.nometadata })); if (isNotEmptyArray(requestResult && requestResult.value)) { results = requestResult.value.map(f => ({ Name: f.Folder.Name, ServerRelativeUrl: normalizeUrl(f.Folder.ServerRelativeUrl) })); } } catch (e) { //Issue 7543 throttled library with lots of items will fail so return empty array logger.error(`Could not get folders from ${listIdOrTitle}, check network for more infromation.`); } return results; } export async function GetFolder(siteUrl: string, folderUrl: string, options: { allowCache?: boolean, includeFolders?: boolean, includeFiles?: boolean } = {}) { options = { includeFiles: false, includeFolders: false, allowCache: true, ...options }; siteUrl = GetSiteUrl(siteUrl); try { let folderServerRelativeUrl = makeServerRelativeUrl(folderUrl, siteUrl); let restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFolderByServerRelativeUrl('${encodeURIComponentEX(folderServerRelativeUrl)}')`; if (options.includeFiles === true || options.includeFolders === true) { let expand = []; if (options.includeFiles) { expand.push("Files"); } if (options.includeFolders) { expand.push("Folders"); } restUrl += `?$expand=${expand.join(",")}`; } const result = await GetJson<IFolderInfo>( restUrl, null, { ...(options.allowCache ? mediumLocalCache : noLocalCache), jsonMetadata: jsonTypes.nometadata }); return result; } catch { } return null; } export async function GetFileItemId(siteUrl: string, fileServerRelativeUrl: string) { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFileByServerRelativeUrl('${encodeURIComponentEX(fileServerRelativeUrl)}')/ListItemAllFields/id`; const result = await GetJson<{ value: number; }>(restUrl, null, { jsonMetadata: jsonTypes.nometadata }); return result.value; } export async function GetFileModerationStatus(siteUrl: string, fileServerRelativeUrl: string) { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFileByServerRelativeUrl('${encodeURIComponentEX(fileServerRelativeUrl)}')/ListItemAllFields/OData__ModerationStatus`; const result = await GetJson<{ value: ModerationStatus; }>(restUrl, null, { jsonMetadata: jsonTypes.nometadata }); return result.value; } export async function GetFilePublishingStatus(siteUrl: string, fileServerRelativeUrl: string) { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFileByServerRelativeUrl('${encodeURIComponentEX(fileServerRelativeUrl)}')/level`; const result = await GetJson<{ value: FileLevel; }>(restUrl, null, { jsonMetadata: jsonTypes.nometadata }); return result.value; } export async function GetFileItemInfo(siteUrl: string, fileServerRelativeUrl: string): Promise<{ listId: string; itemId: number; }> { try { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFileByServerRelativeUrl('${encodeURIComponentEX(fileServerRelativeUrl)}')/ListItemAllFields`; const result = await GetJson<{ d: { __metadata: { //returns something like this: uri: string;//"https://x.sharepoint.com/sites/xxx/_api/Web/Lists(guid'6f743572-6620-40e3-b2dd-c8099e73e9c8')/Items(11)" }, Id: number; } }>(restUrl, null, { jsonMetadata: jsonTypes.verbose }); const itemId = result.d.Id; const listId = result.d.__metadata.uri.split("'")[1]; return { listId, itemId }; } catch (e) { return null; } } export async function GetFolderItemInfo(siteUrl: string, folderServerRelativeUrl: string): Promise<{ listId: string; itemId: number; }> { try { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFolderByServerRelativeUrl('${encodeURIComponentEX(folderServerRelativeUrl)}')/ListItemAllFields`; const result = await GetJson<{ d: { __metadata: { //returns something like this: uri: string;//"https://x.sharepoint.com/sites/xxx/_api/Web/Lists(guid'6f743572-6620-40e3-b2dd-c8099e73e9c8')/Items(11)" }, Id: number; } }>(restUrl, null, { jsonMetadata: jsonTypes.verbose }); const itemId = result.d.Id; const listId = result.d.__metadata.uri.split("'")[1]; return { listId, itemId }; } catch (e) { return null; } } interface iWebPartPageProps { /** webpart id */ id: string; /** unique instance id - random guid, or blank to auto-generate */ instanceId?: string; title: string; description: string; dataVersion?: "1.0"; properties: IDictionary<string | boolean> } interface iWebPartPageResult { /** site relative random page name, such as: "SitePages/y2k9xm8v.aspx" */ value: string; } /** Creates a modern single app page and return its URL. if a file in that name exists, it will return one with (1) appended to it. */ export async function CreateAppPage(siteUrl: string, info: { /** file name, without extension */ name: string; webPartDataAsJson: iWebPartPageProps }) { //read more: //https://petelus.sharepoint.com/sites/CMSTest/_api/SitePages/Pages/CreateAppPage //https://spblog.net/post/2019/03/05/what-s-new-and-what-s-changed-in-sharepoint-online-rest-api-in-january-february-2019 function getFileServerRelativeUrl(siteRelative: string) { const fileRelativeUrl = makeServerRelativeUrl(`${siteUrl}${siteRelative}`); return fileRelativeUrl; } let webPartDataAsJson = info.webPartDataAsJson; if (isNullOrEmptyString(webPartDataAsJson.instanceId)) webPartDataAsJson.instanceId = newGuid(); if (isNullOrEmptyString(webPartDataAsJson.dataVersion)) webPartDataAsJson.dataVersion = "1.0"; return logger.groupAsync("CreateAppPage", async log => { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/SitePages/Pages/CreateAppPage`; const result = await GetJson<iWebPartPageResult>(restUrl, jsonStringify({ webPartDataAsJson: jsonStringify(webPartDataAsJson) }), { method: 'POST', jsonMetadata: jsonTypes.nometadata }); log(`created page`); log(jsonStringify(result)); let fileRelativeUrl = getFileServerRelativeUrl(result.value); const fileId = await GetFileItemId(siteUrl, fileRelativeUrl); const updateRestUrl = `${GetRestBaseUrl(siteUrl)}/SitePages/Pages/UpdateAppPage`; const updateResult = await GetJson<iWebPartPageResult>(updateRestUrl, jsonStringify({ pageId: fileId, title: info.name, webPartDataAsJson: jsonStringify(webPartDataAsJson) }), { method: 'POST', jsonMetadata: jsonTypes.nometadata }); log(`updated page`); log(jsonStringify(updateResult)); fileRelativeUrl = getFileServerRelativeUrl(updateResult.value); return fileRelativeUrl; }); } /** Move a file to a new name/url, this API allows for changing file extension as well */ export async function MoveFile(siteUrl: string, currentServerRelativeUrl: string, targetServerRelativeUrl: string, options?: { overwrite?: boolean; /** set to true to automatically find the next available file name. uploading file.ext to a folder that has that file will upload a file named file.1.ext instead */ autoRename?: boolean; }) { return CopyOrMoveFile(siteUrl, currentServerRelativeUrl, targetServerRelativeUrl, "move", options); //this does NOT allow to change the file extension. only file name. // return UpdateItem(siteUrl, listIdOrTitle, itemId, { // FileLeafRef: newFileName "hello.txt" >> "hello.md" won't work. // }); } /** Copy a file to a new name/url, this API allows for changing file extension as well */ export async function CopyFile(siteUrl: string, currentServerRelativeUrl: string, targetServerRelativeUrl: string, options?: { overwrite?: boolean; /** set to true to automatically find the next available file name. uploading file.ext to a folder that has that file will upload a file named file.1.ext instead */ autoRename?: boolean; }) { return CopyOrMoveFile(siteUrl, currentServerRelativeUrl, targetServerRelativeUrl, "copy", options); } async function CopyOrMoveFile(siteUrl: string, currentServerRelativeUrl: string, targetServerRelativeUrl: string, action: "copy" | "move", options?: { overwrite?: boolean; /** set to true to automatically find the next available file name. uploading file.ext to a folder that has that file will upload a file named file.1.ext instead */ autoRename?: boolean; }) { try { if (options && options.autoRename) { let targetParts = targetServerRelativeUrl.split('/'); let fileName = targetParts.pop(); let targetFolderUrl = targetParts.join('/'); //get all files from this folder and find the next available name let files = await GetFolderFiles(siteUrl, targetFolderUrl); let fileNames = files.map(f => f.Name.toLowerCase()); let counter = 0; let originalName = fileName.split('.'); originalName.splice(originalName.length - 1, 0, counter.toString()); while (fileNames.includes(fileName.toLowerCase())) { counter++; originalName[originalName.length - 2] = counter.toString(); fileName = originalName.join('.'); } targetServerRelativeUrl = `${targetFolderUrl}/${fileName}`; } let url = `${GetRestBaseUrl(siteUrl)}/web/getfilebyserverrelativeurl('${currentServerRelativeUrl}')/` if (action === "copy") { url += `copyto(strNewUrl='${targetServerRelativeUrl}',bOverwrite=${options && options.overwrite ? "true" : "false"})`; } else { url += `moveto(newurl='${targetServerRelativeUrl}',flags=${options && options.overwrite ? 1 : 0})`; } let result = await GetJson(url, undefined, { method: "POST", jsonMetadata: jsonTypes.nometadata }); logger.json(result, "CopyOrMoveFile"); return true; } catch (e) { logger.json(e, "CopyOrMoveFile"); return false; } //this does NOT allow to change the file extension. only file name. // return UpdateItem(siteUrl, listIdOrTitle, itemId, { // FileLeafRef: newFileName "hello.txt" >> "hello.md" won't work. // }); }