UNPKG

@pnp/sp

Version:

pnp - provides a fluent api for working with SharePoint REST

1,145 lines 45 kB
import { body, headers } from "@pnp/queryable"; import { getGUID, hOP, stringIsNullOrEmpty, objectDefinedNotNull, combine, isUrlAbsolute, isArray } from "@pnp/core"; import { Item } from "../items/types.js"; import { _SPQueryable, SPQueryable, SPCollection } from "../spqueryable.js"; import { List } from "../lists/types.js"; import { odataUrlFrom } from "../utils/odata-url-from.js"; import { Web } from "../webs/types.js"; import { extractWebUrl } from "../utils/extract-web-url.js"; import { Site } from "../sites/types.js"; import { spPost } from "../operations.js"; import { getNextOrder, reindex } from "./funcs.js"; import "../files/web.js"; import "../comments/item.js"; import { createBatch } from "../batching.js"; /** * Page promotion state */ export var PromotedState; (function (PromotedState) { /** * Regular client side page */ PromotedState[PromotedState["NotPromoted"] = 0] = "NotPromoted"; /** * Page that will be promoted as news article after publishing */ PromotedState[PromotedState["PromoteOnPublish"] = 1] = "PromoteOnPublish"; /** * Page that is promoted as news article */ PromotedState[PromotedState["Promoted"] = 2] = "Promoted"; })(PromotedState || (PromotedState = {})); /** * Represents the data and methods associated with client side "modern" pages */ export class _ClientsidePage extends _SPQueryable { /** * PLEASE DON'T USE THIS CONSTRUCTOR DIRECTLY, thank you 🐇 */ constructor(base, path, json, noInit = false, sections = [], commentsDisabled = false) { super(base, path); this.json = json; this.sections = sections; this.commentsDisabled = commentsDisabled; this._bannerImageDirty = false; this._bannerImageThumbnailUrlDirty = false; this.parentUrl = ""; // we need to rebase the url to always be the web url plus the path // Queryable handles the correct parsing of the SPInit, and we pull // the weburl and combine with the supplied path, which is unique // to how ClientsitePages works. This class is a special case. this._url = combine(extractWebUrl(this._url), path); // set a default page settings slice this._pageSettings = { controlType: 0, pageSettingsSlice: { isDefaultDescription: true, isDefaultThumbnail: true } }; // set a default layout part this._layoutPart = _ClientsidePage.getDefaultLayoutPart(); if (typeof json !== "undefined" && !noInit) { this.fromJSON(json); } } static getDefaultLayoutPart() { return { dataVersion: "1.4", description: "Title Region Description", id: "cbe7b0a9-3504-44dd-a3a3-0e5cacd07788", instanceId: "cbe7b0a9-3504-44dd-a3a3-0e5cacd07788", properties: { authorByline: [], authors: [], layoutType: "FullWidthImage", showPublishDate: false, showTopicHeader: false, textAlignment: "Left", title: "", topicHeader: "", enableGradientEffect: true, }, reservedHeight: 280, serverProcessedContent: { htmlStrings: {}, searchablePlainTexts: {}, imageSources: {}, links: {} }, title: "Title area", }; } get pageLayout() { return this.json.PageLayoutType; } set pageLayout(value) { this.json.PageLayoutType = value; } get bannerImageUrl() { return this.json.BannerImageUrl; } set bannerImageUrl(value) { this.setBannerImage(value); } get thumbnailUrl() { return this._pageSettings.pageSettingsSlice.isDefaultThumbnail ? this.json.BannerImageUrl : this.json.BannerThumbnailUrl; } set thumbnailUrl(value) { this.json.BannerThumbnailUrl = value; this._bannerImageThumbnailUrlDirty = true; this._pageSettings.pageSettingsSlice.isDefaultThumbnail = false; } get topicHeader() { return objectDefinedNotNull(this.json.TopicHeader) ? this.json.TopicHeader : ""; } set topicHeader(value) { this.json.TopicHeader = value; this._layoutPart.properties.topicHeader = value; if (stringIsNullOrEmpty(value)) { this.showTopicHeader = false; } } get title() { return this.json.Title; } set title(value) { this.json.Title = value; this._layoutPart.properties.title = value; } get reservedHeight() { return this._layoutPart.reservedHeight; } set reservedHeight(value) { this._layoutPart.reservedHeight = value; } get description() { return this.json.Description; } set description(value) { if (!stringIsNullOrEmpty(value) && value.length > 255) { throw Error("Modern Page description is limited to 255 chars."); } this.json.Description = value; if (!hOP(this._pageSettings, "htmlAttributes")) { this._pageSettings.htmlAttributes = []; } if (this._pageSettings.htmlAttributes.indexOf("modifiedDescription") < 0) { this._pageSettings.htmlAttributes.push("modifiedDescription"); } this._pageSettings.pageSettingsSlice.isDefaultDescription = false; } get layoutType() { return this._layoutPart.properties.layoutType; } set layoutType(value) { this._layoutPart.properties.layoutType = value; } get headerTextAlignment() { return this._layoutPart.properties.textAlignment; } set headerTextAlignment(value) { this._layoutPart.properties.textAlignment = value; } get showTopicHeader() { return this._layoutPart.properties.showTopicHeader; } set showTopicHeader(value) { this._layoutPart.properties.showTopicHeader = value; } get showPublishDate() { return this._layoutPart.properties.showPublishDate; } set showPublishDate(value) { this._layoutPart.properties.showPublishDate = value; } get hasVerticalSection() { return this.sections.findIndex(s => s.layoutIndex === 2) > -1; } get authorByLine() { if (isArray(this._layoutPart.properties.authorByline) && this._layoutPart.properties.authorByline.length > 0) { return this._layoutPart.properties.authorByline[0]; } return null; } get verticalSection() { if (this.hasVerticalSection) { return this.addVerticalSection(); } return null; } /** * Add a section to this page */ addSection() { const section = new CanvasSection(this, getNextOrder(this.sections), 1); this.sections.push(section); return section; } /** * Add a section to this page */ addVerticalSection() { // we can only have one vertical section so we find it if it exists const sectionIndex = this.sections.findIndex(s => s.layoutIndex === 2); if (sectionIndex > -1) { return this.sections[sectionIndex]; } const section = new CanvasSection(this, getNextOrder(this.sections), 2); this.sections.push(section); return section; } /** * Loads this instance from the appropriate JSON data * * @param pageData JSON data to load (replaces any existing data) */ fromJSON(pageData) { this.json = pageData; const canvasControls = JSON.parse(pageData.CanvasContent1); const layouts = JSON.parse(pageData.LayoutWebpartsContent); if (layouts && layouts.length > 0) { this._layoutPart = layouts[0]; } this.setControls(canvasControls); return this; } /** * Loads this page's content from the server */ async load() { const item = await this.getItem("Id", "CommentsDisabled"); const pageData = await SPQueryable(this, `_api/sitepages/pages(${item.Id})`)(); this.commentsDisabled = item.CommentsDisabled; return this.fromJSON(pageData); } /** * Persists the content changes (sections, columns, and controls) [does not work with batching] * * @param publish If true the page is published, if false the changes are persisted to SharePoint but not published [Default: true] */ async save(publish = true) { if (this.json.Id === null) { throw Error("The id for this page is null. If you want to create a new page, please use ClientSidePage.Create"); } const previewPartialUrl = "_layouts/15/getpreview.ashx"; // If new banner image, and banner image url is not in getpreview.ashx format if (this._bannerImageDirty && !this.bannerImageUrl.includes(previewPartialUrl)) { const serverRelativePath = this.bannerImageUrl; let imgInfo; let webUrl; const web = Web(this); const [batch, execute] = createBatch(web); web.using(batch); web.getFileByServerRelativePath(serverRelativePath.replace(/%20/ig, " ")) .select("ListId", "WebId", "UniqueId", "Name", "SiteId")().then(r1 => imgInfo = r1); web.select("Url")().then(r2 => webUrl = r2.Url); // we know the .then calls above will run before execute resolves, ensuring the vars are set await execute(); const f = SPQueryable(webUrl, previewPartialUrl); f.query.set("guidSite", `${imgInfo.SiteId}`); f.query.set("guidWeb", `${imgInfo.WebId}`); f.query.set("guidFile", `${imgInfo.UniqueId}`); this.bannerImageUrl = f.toRequestUrl(); if (!objectDefinedNotNull(this._layoutPart.serverProcessedContent)) { this._layoutPart.serverProcessedContent = {}; } this._layoutPart.serverProcessedContent.imageSources = { imageSource: serverRelativePath }; if (!objectDefinedNotNull(this._layoutPart.serverProcessedContent.customMetadata)) { this._layoutPart.serverProcessedContent.customMetadata = {}; } this._layoutPart.serverProcessedContent.customMetadata.imageSource = { listId: imgInfo.ListId, siteId: imgInfo.SiteId, uniqueId: imgInfo.UniqueId, webId: imgInfo.WebId, }; this._layoutPart.properties.webId = imgInfo.WebId; this._layoutPart.properties.siteId = imgInfo.SiteId; this._layoutPart.properties.listId = imgInfo.ListId; this._layoutPart.properties.uniqueId = imgInfo.UniqueId; } // we try and check out the page for the user if (!this.json.IsPageCheckedOutToCurrentUser) { await spPost(ClientsidePage(this, `_api/sitepages/pages(${this.json.Id})/checkoutpage`)); } // create the body for the save request let saveBody = { AuthorByline: this.json.AuthorByline || [], CanvasContent1: this.getCanvasContent1(), Description: this.description, LayoutWebpartsContent: this.getLayoutWebpartsContent(), Title: this.title, TopicHeader: this.topicHeader, BannerImageUrl: this.bannerImageUrl, }; if (this._bannerImageDirty || this._bannerImageThumbnailUrlDirty) { const bannerImageUrlValue = this._bannerImageThumbnailUrlDirty ? this.thumbnailUrl : this.bannerImageUrl; saveBody = { BannerImageUrl: bannerImageUrlValue, ...saveBody, }; } const updater = ClientsidePage(this, `_api/sitepages/pages(${this.json.Id})/savepage`); await spPost(updater, headers({ "if-match": "*" }, body(saveBody))); let r = true; if (publish) { r = await spPost(ClientsidePage(this, `_api/sitepages/pages(${this.json.Id})/publish`)); if (r) { this.json.IsPageCheckedOutToCurrentUser = false; } } this._bannerImageDirty = false; this._bannerImageThumbnailUrlDirty = false; // we need to ensure we reload from the latest data to ensure all urls are updated and current in the object (expecially for new pages) await this.load(); return r; } /** * Discards the checkout of this page */ async discardPageCheckout() { if (this.json.Id === null) { throw Error("The id for this page is null. If you want to create a new page, please use ClientSidePage.Create"); } const d = await spPost(ClientsidePage(this, `_api/sitepages/pages(${this.json.Id})/discardPage`)); this.fromJSON(d); } /** * Promotes this page as a news item */ async promoteToNews() { return this.promoteNewsImpl("promoteToNews"); } // API is currently broken on server side // public async demoteFromNews(): Promise<boolean> { // return this.promoteNewsImpl("demoteFromNews"); // } /** * Finds a control by the specified instance id * * @param id Instance id of the control to find */ findControlById(id) { return this.findControl((c) => c.id === id); } /** * Finds a control within this page's control tree using the supplied predicate * * @param predicate Takes a control and returns true or false, if true that control is returned by findControl */ findControl(predicate) { // check all sections for (let i = 0; i < this.sections.length; i++) { // check all columns for (let j = 0; j < this.sections[i].columns.length; j++) { // check all controls for (let k = 0; k < this.sections[i].columns[j].controls.length; k++) { // check to see if the predicate likes this control if (predicate(this.sections[i].columns[j].controls[k])) { return this.sections[i].columns[j].controls[k]; } } } } // we found nothing so give nothing back return null; } /** * Creates a new page with all of the content copied from this page * * @param web The web where we will create the copy * @param pageName The file name of the new page * @param title The title of the new page * @param publish If true the page will be published (Default: true) */ async copy(web, pageName, title, publish = true, promotedState) { const page = await CreateClientsidePage(web, pageName, title, this.pageLayout, promotedState); return this.copyTo(page, publish); } /** * Copies the content from this page to the supplied page instance NOTE: fully overwriting any previous content!! * * @param page Page whose content we replace with this page's content * @param publish If true the page will be published after the copy, if false it will be saved but left unpublished (Default: true) */ async copyTo(page, publish = true) { // we know the method is on the class - but it is protected so not part of the interface page.setControls(this.getControls()); // copy page properties if (this._layoutPart.properties) { if (hOP(this._layoutPart.properties, "topicHeader")) { page.topicHeader = this._layoutPart.properties.topicHeader; } if (hOP(this._layoutPart.properties, "imageSourceType")) { page._layoutPart.properties.imageSourceType = this._layoutPart.properties.imageSourceType; } if (hOP(this._layoutPart.properties, "layoutType")) { page._layoutPart.properties.layoutType = this._layoutPart.properties.layoutType; } if (hOP(this._layoutPart.properties, "textAlignment")) { page._layoutPart.properties.textAlignment = this._layoutPart.properties.textAlignment; } if (hOP(this._layoutPart.properties, "showTopicHeader")) { page._layoutPart.properties.showTopicHeader = this._layoutPart.properties.showTopicHeader; } if (hOP(this._layoutPart.properties, "showPublishDate")) { page._layoutPart.properties.showPublishDate = this._layoutPart.properties.showPublishDate; } if (hOP(this._layoutPart.properties, "enableGradientEffect")) { page._layoutPart.properties.enableGradientEffect = this._layoutPart.properties.enableGradientEffect; } } // we need to do some work to set the banner image url in the copied page if (!stringIsNullOrEmpty(this.json.BannerImageUrl)) { // use a URL to parse things for us const url = new URL(this.json.BannerImageUrl); // helper function to translate the guid strings into properly formatted guids with dashes const makeGuid = (s) => s.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/g, "$1-$2-$3-$4-$5"); // protect against errors because the serverside impl has changed, we'll just skip if (url.searchParams.has("guidSite") && url.searchParams.has("guidWeb") && url.searchParams.has("guidFile")) { const guidSite = makeGuid(url.searchParams.get("guidSite")); const guidWeb = makeGuid(url.searchParams.get("guidWeb")); const guidFile = makeGuid(url.searchParams.get("guidFile")); const site = Site(this); const id = await site.select("Id")(); // the site guid must match the current site's guid or we are unable to set the image if (id.Id === guidSite) { const openWeb = await site.openWebById(guidWeb); const file = await openWeb.web.getFileById(guidFile).select("ServerRelativeUrl")(); const props = {}; if (this._layoutPart.properties) { if (hOP(this._layoutPart.properties, "translateX")) { props.translateX = this._layoutPart.properties.translateX; } if (hOP(this._layoutPart.properties, "translateY")) { props.translateY = this._layoutPart.properties.translateY; } if (hOP(this._layoutPart.properties, "imageSourceType")) { props.imageSourceType = this._layoutPart.properties.imageSourceType; } if (hOP(this._layoutPart.properties, "altText")) { props.altText = this._layoutPart.properties.altText; } } page.setBannerImage(file.ServerRelativeUrl, props); } } } await page.save(publish); return page; } /** * Sets the modern page banner image * * @param url Url of the image to display * @param altText Alt text to describe the image * @param bannerProps Additional properties to control display of the banner */ setBannerImage(url, props) { if (isUrlAbsolute(url)) { // do our best to make this a server relative url by removing the x.sharepoint.com part url = url.replace(/^https?:\/\/[a-z0-9.]*?\.[a-z]{2,3}\//i, "/"); } this.json.BannerImageUrl = url; // update serverProcessedContent (page behavior change 2021-Oct-13) this._layoutPart.serverProcessedContent = { imageSources: { imageSource: url } }; this._bannerImageDirty = true; /* setting the banner image resets the thumbnail image (matching UI functionality) but if the thumbnail is dirty they are likely trying to set them both to different values, so we allow that here. Also allows the banner image to be updated safely with the calculated one in save() */ if (!this._bannerImageThumbnailUrlDirty) { this.thumbnailUrl = url; this._pageSettings.pageSettingsSlice.isDefaultThumbnail = true; } // this seems to always be true, so default this._layoutPart.properties.imageSourceType = 2; if (objectDefinedNotNull(props)) { if (hOP(props, "translateX")) { this._layoutPart.properties.translateX = props.translateX; } if (hOP(props, "translateY")) { this._layoutPart.properties.translateY = props.translateY; } if (hOP(props, "imageSourceType")) { this._layoutPart.properties.imageSourceType = props.imageSourceType; } if (hOP(props, "altText")) { this._layoutPart.properties.altText = props.altText; } } } /** * Sets the banner image url from an external source. You must call save to persist the changes * * @param url absolute url of the external file * @param props optional set of properties to control display of the banner image */ async setBannerImageFromExternalUrl(url, props) { // validate and parse our input url const fileUrl = new URL(url); // get our page name without extension, used as a folder name when creating the file const pageName = this.json.FileName.replace(/\.[^/.]+$/, ""); // get the filename we will use const filename = fileUrl.pathname.split(/[\\/]/i).pop(); const request = ClientsidePage(this, "_api/sitepages/AddImageFromExternalUrl"); request.query.set("imageFileName", `'${filename}'`); request.query.set("pageName", `'${pageName}'`); request.query.set("externalUrl", `'${url}'`); request.select("ServerRelativeUrl"); const result = await spPost(request); // set with the newly created relative url this.setBannerImage(result.ServerRelativeUrl, props); } /** * Sets the authors for this page from the supplied list of user integer ids * * @param authorId The integer id of the user to set as the author */ async setAuthorById(authorId) { const userLoginData = await SPCollection([this, extractWebUrl(this.toUrl())], "/_api/web/siteusers") .filter(`Id eq ${authorId}`) .select("LoginName")(); if (userLoginData.length < 1) { throw Error(`Could not find user with id ${authorId}.`); } return this.setAuthorByLoginName(userLoginData[0].LoginName); } /** * Sets the authors for this page from the supplied list of user integer ids * * @param authorLoginName The login name of the user (ex: i:0#.f|membership|name@tenant.com) */ async setAuthorByLoginName(authorLoginName) { const userLoginData = await SPCollection([this, extractWebUrl(this.toUrl())], "/_api/web/siteusers") .filter(`LoginName eq '${authorLoginName}'`) .select("UserPrincipalName", "Title")(); if (userLoginData.length < 1) { throw Error(`Could not find user with login name '${authorLoginName}'.`); } this.json.AuthorByline = [userLoginData[0].UserPrincipalName]; this._layoutPart.properties.authorByline = [userLoginData[0].UserPrincipalName]; this._layoutPart.properties.authors = [{ id: authorLoginName, name: userLoginData[0].Title, role: "", upn: userLoginData[0].UserPrincipalName, }]; } /** * Gets the list item associated with this clientside page * * @param selects Specific set of fields to include when getting the item */ async getItem(...selects) { const initer = ClientsidePage(this, "/_api/lists/EnsureClientRenderedSitePagesLibrary").select("EnableModeration", "EnableMinorVersions", "Id"); const listData = await spPost(initer); const item = List([this, listData["odata.id"]]).items.getById(this.json.Id); const itemData = await item.select(...selects)(); return Object.assign(Item([this, odataUrlFrom(itemData)]), itemData); } /** * Recycle this page */ async recycle() { const item = await this.getItem(); await item.recycle(); } /** * Delete this page */ async delete() { const item = await this.getItem(); await item.delete(); } /** * Schedules a page for publishing * * @param publishDate Date to publish the item * @returns Version which was scheduled to be published */ async schedulePublish(publishDate) { return spPost(ClientsidePage(this, `_api/sitepages/pages(${this.json.Id})/SchedulePublish`), body({ sitePage: { PublishStartDate: publishDate }, })); } /** * Saves a copy of this page as a template in this library's Templates folder * * @param publish If true the template is published, false the template is not published (default: true) * @returns IClientsidePage instance representing the new template page */ async saveAsTemplate(publish = true) { const data = await spPost(ClientsidePage(this, `_api/sitepages/pages(${this.json.Id})/SavePageAsTemplate`)); const page = ClientsidePage(this, null, data); page.title = this.title; await page.save(publish); return page; } /** * Share this Page's Preview content by Email * * @param emails Set of emails to which the preview is shared * @param message The message to include * @returns void */ share(emails, message) { return spPost(ClientsidePage(this, "_api/SP.Publishing.RichSharing/SharePageByEmail"), body({ recipientEmails: emails, message, url: this.json.AbsoluteUrl, })); } getCanvasContent1() { return JSON.stringify(this.getControls()); } getLayoutWebpartsContent() { if (this._layoutPart) { return JSON.stringify([this._layoutPart]); } else { return JSON.stringify(null); } } setControls(controls) { // reset the sections this.sections = []; if (controls && controls.length) { for (let i = 0; i < controls.length; i++) { // if no control type is present this is a column which we give type 0 to let us process it const controlType = hOP(controls[i], "controlType") ? controls[i].controlType : 0; switch (controlType) { case 0: // empty canvas column or page settings if (hOP(controls[i], "pageSettingsSlice")) { this._pageSettings = controls[i]; } else { // we have an empty column this.mergeColumnToTree(new CanvasColumn(controls[i])); } break; case 3: { const part = new ClientsideWebpart(controls[i]); this.mergePartToTree(part, part.data.position); break; } case 4: { const textData = controls[i]; const text = new ClientsideText(textData.innerHTML, textData); this.mergePartToTree(text, text.data.position); break; } } } reindex(this.sections); } } getControls() { // reindex things reindex(this.sections); // rollup the control changes const canvasData = []; this.sections.forEach(section => { section.columns.forEach(column => { if (column.controls.length < 1) { // empty column canvasData.push({ displayMode: column.data.displayMode, emphasis: this.getEmphasisObj(section.emphasis), position: column.data.position, }); } else { column.controls.forEach(control => { control.data.emphasis = this.getEmphasisObj(section.emphasis); canvasData.push(this.specialSaveHandling(control).data); }); } }); }); canvasData.push(this._pageSettings); return canvasData; } getEmphasisObj(value) { if (value < 1 || value > 3) { return {}; } return { zoneEmphasis: value }; } async promoteNewsImpl(method) { if (this.json.Id === null) { throw Error("The id for this page is null."); } // per bug #858 if we promote before we have ever published the last published date will // forever not be updated correctly in the modern news web part. Because this will affect very // few folks we just go ahead and publish for them here as that is likely what they intended. if (stringIsNullOrEmpty(this.json.VersionInfo.LastVersionCreatedBy)) { const lastPubData = new Date(this.json.VersionInfo.LastVersionCreated); // no modern page should reasonable be published before the year 2000 :) if (lastPubData.getFullYear() < 2000) { await this.save(true); } } return spPost(ClientsidePage(this, `_api/sitepages/pages(${this.json.Id})/${method}`)); } /** * Merges the control into the tree of sections and columns for this page * * @param control The control to merge */ mergePartToTree(control, positionData) { var _a, _b, _c; let column = null; let sectionFactor = 12; let sectionIndex = 0; let zoneIndex = 0; let layoutIndex = 1; // handle case where we don't have position data (shouldn't happen?) if (positionData) { if (hOP(positionData, "zoneIndex")) { zoneIndex = positionData.zoneIndex; } if (hOP(positionData, "sectionIndex")) { sectionIndex = positionData.sectionIndex; } if (hOP(positionData, "sectionFactor")) { sectionFactor = positionData.sectionFactor; } if (hOP(positionData, "layoutIndex")) { layoutIndex = positionData.layoutIndex; } } const zoneEmphasis = (_c = (_b = (_a = control.data) === null || _a === void 0 ? void 0 : _a.emphasis) === null || _b === void 0 ? void 0 : _b.zoneEmphasis) !== null && _c !== void 0 ? _c : 0; const section = this.getOrCreateSection(zoneIndex, layoutIndex, zoneEmphasis); const columns = section.columns.filter(c => c.order === sectionIndex); if (columns.length < 1) { column = section.addColumn(sectionFactor, layoutIndex); } else { column = columns[0]; } control.column = column; column.addControl(control); } /** * Merges the supplied column into the tree * * @param column Column to merge * @param position The position data for the column */ mergeColumnToTree(column) { var _a, _b; const order = hOP(column.data, "position") && hOP(column.data.position, "zoneIndex") ? column.data.position.zoneIndex : 0; const layoutIndex = hOP(column.data, "position") && hOP(column.data.position, "layoutIndex") ? column.data.position.layoutIndex : 1; const section = this.getOrCreateSection(order, layoutIndex, ((_b = (_a = column.data) === null || _a === void 0 ? void 0 : _a.emphasis) === null || _b === void 0 ? void 0 : _b.zoneEmphasis) || 0); column.section = section; section.columns.push(column); } /** * Handle the logic to get or create a section based on the supplied order and layoutIndex * * @param order Section order * @param layoutIndex Layout Index (1 === normal, 2 === vertical section) * @param emphasis The section emphasis */ getOrCreateSection(order, layoutIndex, emphasis) { let section = null; const sections = this.sections.filter(s => s.order === order && s.layoutIndex === layoutIndex); if (sections.length < 1) { section = layoutIndex === 2 ? this.addVerticalSection() : this.addSection(); section.order = order; section.emphasis = emphasis; } else { section = sections[0]; } return section; } /** * Based on issue #1690 we need to take special case actions to ensure some things * can be saved properly without breaking existing pages. * * @param control The control we are ensuring is "ready" to be saved */ specialSaveHandling(control) { var _a, _b, _c; // this is to handle the special case in issue #1690 // must ensure that searchablePlainTexts values have < replaced with &lt; in links web part // For #2561 need to process for code snippet webpart and any control && (<any>control).data.webPartId === "c70391ea-0b10-4ee9-b2b4-006d3fcad0cd" if (control.data.controlType === 3) { const texts = ((_c = (_b = (_a = control.data) === null || _a === void 0 ? void 0 : _a.webPartData) === null || _b === void 0 ? void 0 : _b.serverProcessedContent) === null || _c === void 0 ? void 0 : _c.searchablePlainTexts) || null; if (objectDefinedNotNull(texts)) { const keys = Object.getOwnPropertyNames(texts); for (let i = 0; i < keys.length; i++) { texts[keys[i]] = texts[keys[i]].replace(/</ig, "&lt;"); control.data.webPartData.serverProcessedContent.searchablePlainTexts = texts; } } } return control; } } /** * Invokable factory for IClientSidePage instances */ const ClientsidePage = (base, path, json, noInit = false, sections = [], commentsDisabled = false) => { return new _ClientsidePage(base, path, json, noInit, sections, commentsDisabled); }; /** * Loads a client side page from the supplied IFile instance * * @param file Source IFile instance */ export const ClientsidePageFromFile = async (file) => { const item = await file.getItem(); const page = ClientsidePage([file, extractWebUrl(file.toUrl())], "", { Id: item.Id }, true); return page.load(); }; /** * Creates a new client side page * * @param web The web or list * @param pageName The name of the page (filename) * @param title The page's title * @param PageLayoutType Layout to use when creating the page */ export const CreateClientsidePage = async (web, pageName, title, PageLayoutType = "Article", promotedState = 0) => { // patched because previously we used the full page name with the .aspx at the end // this allows folk's existing code to work after the re-write to the new API pageName = pageName.replace(/\.aspx$/i, ""); // initialize the page, at this point a checked-out page with a junk filename will be created. const pageInitData = await spPost(ClientsidePage(web, "_api/sitepages/pages"), body({ PageLayoutType, PromotedState: promotedState, })); // now we can init our page with the save data const newPage = ClientsidePage(web, "", pageInitData); newPage.title = pageName; await newPage.save(false); newPage.title = title; return newPage; }; export class CanvasSection { constructor(page, order, layoutIndex, columns = [], _emphasis = 0) { this.page = page; this.columns = columns; this._emphasis = _emphasis; this._memId = getGUID(); this._order = order; this._layoutIndex = layoutIndex; } get order() { return this._order; } set order(value) { this._order = value; for (let i = 0; i < this.columns.length; i++) { this.columns[i].data.position.zoneIndex = value; } } get layoutIndex() { return this._layoutIndex; } set layoutIndex(value) { this._layoutIndex = value; for (let i = 0; i < this.columns.length; i++) { this.columns[i].data.position.layoutIndex = value; } } /** * Default column (this.columns[0]) for this section */ get defaultColumn() { if (this.columns.length < 1) { this.addColumn(12); } return this.columns[0]; } /** * Adds a new column to this section */ addColumn(factor, layoutIndex = this.layoutIndex) { const column = new CanvasColumn(); column.section = this; column.data.position.zoneIndex = this.order; column.data.position.layoutIndex = layoutIndex; column.data.position.sectionFactor = factor; column.order = getNextOrder(this.columns); this.columns.push(column); return column; } /** * Adds a control to the default column for this section * * @param control Control to add to the default column */ addControl(control) { this.defaultColumn.addControl(control); return this; } get emphasis() { return this._emphasis; } set emphasis(value) { this._emphasis = value; } /** * Removes this section and all contained columns and controls from the collection */ remove() { this.page.sections = this.page.sections.filter(section => section._memId !== this._memId); reindex(this.page.sections); } } export class CanvasColumn { constructor(json = JSON.parse(JSON.stringify(CanvasColumn.Default)), controls = []) { this.json = json; this.controls = controls; this._section = null; this._memId = getGUID(); } get data() { return this.json; } get section() { return this._section; } set section(section) { this._section = section; } get order() { return this.data.position.sectionIndex; } set order(value) { this.data.position.sectionIndex = value; for (let i = 0; i < this.controls.length; i++) { this.controls[i].data.position.zoneIndex = this.data.position.zoneIndex; this.controls[i].data.position.layoutIndex = this.data.position.layoutIndex; this.controls[i].data.position.sectionIndex = value; } } get factor() { return this.data.position.sectionFactor; } set factor(value) { this.data.position.sectionFactor = value; } addControl(control) { control.column = this; this.controls.push(control); return this; } getControl(index) { return this.controls[index]; } remove() { this.section.columns = this.section.columns.filter(column => column._memId !== this._memId); reindex(this.section.columns); } } CanvasColumn.Default = { controlType: 0, displayMode: 2, emphasis: {}, position: { layoutIndex: 1, sectionFactor: 12, sectionIndex: 1, zoneIndex: 1, }, }; export class ColumnControl { constructor(json) { this.json = json; } get id() { return this.json.id; } get data() { return this.json; } get column() { return this._column; } set column(value) { this._column = value; this.onColumnChange(this._column); } remove() { this.column.controls = this.column.controls.filter(control => control.id !== this.id); reindex(this.column.controls); } setData(data) { this.json = data; } } export class ClientsideText extends ColumnControl { constructor(text, json = JSON.parse(JSON.stringify(ClientsideText.Default))) { if (stringIsNullOrEmpty(json.id)) { json.id = getGUID(); json.anchorComponentId = json.id; } super(json); this.text = text; } get text() { return this.data.innerHTML; } set text(value) { this.data.innerHTML = value; } get order() { return this.data.position.controlIndex; } set order(value) { this.data.position.controlIndex = value; } onColumnChange(col) { this.data.position.sectionFactor = col.factor; this.data.position.controlIndex = getNextOrder(col.controls); this.data.position.zoneIndex = col.data.position.zoneIndex; this.data.position.sectionIndex = col.order; this.data.position.layoutIndex = col.data.position.layoutIndex; } } ClientsideText.Default = { addedFromPersistedData: false, anchorComponentId: "", controlType: 4, displayMode: 2, editorType: "CKEditor", emphasis: {}, id: "", innerHTML: "", position: { controlIndex: 1, layoutIndex: 1, sectionFactor: 12, sectionIndex: 1, zoneIndex: 1, }, }; export class ClientsideWebpart extends ColumnControl { constructor(json = JSON.parse(JSON.stringify(ClientsideWebpart.Default))) { super(json); } static fromComponentDef(definition) { const part = new ClientsideWebpart(); part.import(definition); return part; } get title() { return this.data.webPartData.title; } set title(value) { this.data.webPartData.title = value; } get description() { return this.data.webPartData.description; } set description(value) { this.data.webPartData.description = value; } get order() { return this.data.position.controlIndex; } set order(value) { this.data.position.controlIndex = value; } get height() { return this.data.reservedHeight; } set height(value) { this.data.reservedHeight = value; } get width() { return this.data.reservedWidth; } set width(value) { this.data.reservedWidth = value; } get dataVersion() { return this.data.webPartData.dataVersion; } set dataVersion(value) { this.data.webPartData.dataVersion = value; } setProperties(properties) { this.data.webPartData.properties = { ...this.data.webPartData.properties, ...properties, }; return this; } getProperties() { return this.data.webPartData.properties; } setServerProcessedContent(properties) { this.data.webPartData.serverProcessedContent = { ...this.data.webPartData.serverProcessedContent, ...properties, }; return this; } getServerProcessedContent() { return this.data.webPartData.serverProcessedContent; } onColumnChange(col) { this.data.position.sectionFactor = col.factor; this.data.position.controlIndex = getNextOrder(col.controls); this.data.position.zoneIndex = col.data.position.zoneIndex; this.data.position.sectionIndex = col.data.position.sectionIndex; this.data.position.layoutIndex = col.data.position.layoutIndex; } import(component) { const id = getGUID(); const componendId = component.Id.replace(/^\{|\}$/g, "").toLowerCase(); const manifest = JSON.parse(component.Manifest); const preconfiguredEntries = manifest.preconfiguredEntries[0]; this.setData(Object.assign({}, this.data, { id, webPartData: { dataVersion: "1.0", description: preconfiguredEntries.description.default, id: componendId, instanceId: id, properties: preconfiguredEntries.properties, title: preconfiguredEntries.title.default, }, webPartId: componendId, })); } } ClientsideWebpart.Default = { addedFromPersistedData: false, controlType: 3, displayMode: 2, emphasis: {}, id: null, position: { controlIndex: 1, layoutIndex: 1, sectionFactor: 12, sectionIndex: 1, zoneIndex: 1, }, reservedHeight: 500, reservedWidth: 500, webPartData: null, webPartId: null, }; //# sourceMappingURL=types.js.map