diff --git a/.forceignore b/.forceignore index 4923e15..b0ff555 100755 --- a/.forceignore +++ b/.forceignore @@ -12,6 +12,11 @@ package.xml **/__tests__/signals.test.ts appMenus/ -dashboards/ -reports/ -reportTypes/ +# Reports and Report Types +**/reports/** +**/reportTypes/** + +# Dashboards +**/dashboards/** + +**/tw/input.css diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78b4c7f..628ee1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} + - name: Setup node uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: "20" cache: "npm" - run: npm ci - run: npm run build --if-present diff --git a/.prettierrc b/.prettierrc index 18039a0..f4bf55e 100755 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,6 @@ { "trailingComma": "none", "plugins": [ - "prettier-plugin-apex", "@prettier/plugin-xml" ], "overrides": [ diff --git a/README.md b/README.md index d34217e..28a01b3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A simple yet powerful reactive state management solution for Lightning Web Compo Inspired by the Signals technology behind SolidJs, Preact, Svelte 5 Runes and the Vue 3 Composition API, LWC Signals is a -reactive signals for Lightning Web Components that allows you to create reactive data signalss +reactive signals for Lightning Web Components that allows you to create reactive data signals that can be used to share state between components. It features: @@ -19,6 +19,22 @@ It features: - 🔬️ **Small Surface** The API does not offer more than what is needed, keeping the learning curve and bloat to a minimum +The goal is to allow you to create beautiful and complex user experiences, while achieving clean code that +separates concerns and is easy to maintain! + +Easily implement: + +- Reactive data stores +- Undo +- Optimistic updates +- Data caching through storage (localStorage, cookies, etc) + +

+ Kitchen Sink Example +

+ +> To see the code for the example above, check the `examples/shopping-cart` folder. + # Getting Started Copy the `force-app/lwc/signals` folder to your project. @@ -124,9 +140,9 @@ export default class Display extends LightningElement { > to trigger the reactivity. This is because we need the value to be reassigned so that > LWC reactive system can detect the change and update the UI. -
+

Counter Example -

+

### Stacking computed values @@ -203,7 +219,7 @@ export default class ContactInfoForm extends LightningElement { } ``` -**You can create a computed value that depends on both signalss** +**You can create a computed value that depends on both signals** ```html @@ -221,7 +237,7 @@ export default class ContactInfoForm extends LightningElement { // businessCard.js import { LightningElement } from "lwc"; import { $computed } from "c/signals"; -import { accountName, contactName } from "c/demoSignalss"; +import { accountName, contactName } from "c/demoSignals"; export default class BusinessCard extends LightningElement { contactInfo = $computed( @@ -234,9 +250,9 @@ export default class BusinessCard extends LightningElement { } ``` -
+

Counter Example -

+

> ❗ Notice that we are using a property instead of a getter in the `$computed` callback function, because > we need to reassign the value to `this.contactInfo` to trigger the reactivity, as it is a complex object. @@ -346,9 +362,9 @@ type AsyncData = { > 🍪 One benefit of using the `$resource` over declarative Apex or `@wire` is that it keeps track of the loading > state for you, which saves you the effort of having to calculate it yourself. -
+

Fetching From Apex -

+

--- @@ -421,7 +437,7 @@ Let's now create our picklist component that allows the user to select an accoun // accountPicker.js import { LightningElement, track, wire } from "lwc"; import getAccounts from "@salesforce/apex/ResourceController.getAccounts"; -import { selectedAccountId } from "c/demoSignalss"; +import { selectedAccountId } from "c/demoSignals"; export default class AccountPicker extends LightningElement { @track accounts = []; @@ -486,16 +502,16 @@ Now, let's create the component that displays the details of the selected accoun // accountDetails.js import { LightningElement } from "lwc"; import { $computed } from "c/signals"; -import { getAccount } from "c/demoSignalss"; +import { getAccount } from "c/demoSignals"; export default class AccountDetails extends LightningElement { account = $computed(() => (this.account = getAccount.value)).value; } ``` -
+

Account Picker Example -

+

> 🍪 One extra feature of the data returned by the `$resource` function is that when it is reloading the data, the > previous data is still available in the `data` property. This allows you to keep the old value while the new value is @@ -550,6 +566,93 @@ export default class ContactList extends LightningElement { } ``` +### Mutating `$resource` data + +Besides `refetch`, the `$resource` function also returns a `mutate` function that allows you to mutate the data. + +`mutate` is useful when you want to update the data without refetching it (and avoid a trip to the server). + +It receives a single value, which will be set as the new value of the data. The `resource` value will be updated +immediately, the `.loading` property will be set to `false`, and the `.error` property will be set to `null`. + +```javascript +import { $resource } from "c/signals"; + +const { data, mutate } = $resource(asyncFunction); + +mutate("new value"); +``` + +#### Reacting to mutated values + +When using the `mutate` function, you might want to react to the changes in the data. For example, you might now +want to call an Apex function to save the new value to the server, and make sure the data is synced. + +For this, you can provide a function through the options object's `onMutate`. + +The function you provide can receive 3 arguments: + +- The new value +- The old value +- A `mutate` function that you can use the update the data again. This can be used for when you want to update the data + based on what was returned from the server, but you don't want to refetch the data. You SHOULD use this mutate + function over the one returned when creating the `$resource` because this will not trigger `onMutate` once again. + +```javascript +import { $resource } from "c/signals"; +import getContacts from "@salesforce/apex/ContactController.getContacts"; +import saveContacts from "@salesforce/apex/ContactController.saveContacts"; + +const { data, mutate } = $resource( + getContacts, + {}, + { + onMutate: async (newValue, oldValue, mutate) => { + await saveContacts({ contacts: newValue }); + mutate(newValue); + } + } +); +``` + +In the case an error occurs on your server call, the `mutate` you can pass an error object as the second argument to +the `mutate` function. This will set the `.error` property of the `resource` to the error object. + +```javascript +import { $resource } from "c/signals"; + +const { data, mutate } = $resource(asyncFunction); + +try { + await saveContacts({ contacts: newValue }); + mutate(newValue); +} catch (error) { + mutate(null, error); +} +``` + +#### Optimistic updating + +When you mutate a `resource` as exemplified above, you can achieve the concept of optimistic updating. This is when you +update the value immediately before the server responds, and then update the value again when the server responds. + +Optimistically updating the value can provide a better user experience by making the UI feel more responsive, but it +can also lead to inconsistencies if the server responds with an error. So if you wish to turn this off, and +manage updating the value yourself, either by `refetching` or by using an `onMutate` function, you can set the +`optimisticMutate` option to `false`. + +```javascript +import { $resource } from "c/signals"; + +const { data, refetch, mutate } = $resource( + asyncFunction, + {}, + { + optimisticMutate: false + } +); +``` + ## Storage By default, any created signal is stored in memory and will be lost when the component is destroyed. This behavior @@ -568,7 +671,7 @@ The following storage helpers are available by default: - `useCookies(key: string, expires?: Date)`: Stores the signal in a cookie with the given key. You can also pass an optional `expires` parameter to set the expiration date of the cookie -### Creating custom storage +### Creating a custom storage The `storage` option receives a function that defines the behavior for where the data should be stored. This means you can create your own custom storage solution by passing a function with the following @@ -633,6 +736,24 @@ storage, and the setter should set the value in the storage. Notice that any additional properties you add to the object returned by `createStorage` will be available in the returned object. That is how we can add the `undo` function to the `counter` signal and use it to undo the changes. +## Examples + +You can find full working examples in the `examples` folder. + +For a full kitchen sink example that combines all the concepts, you can check the `shopping-cart` example. + +It includes: + +- Getting data from the server +- Optimistic updates by updating the local value on change +- Re-updating the value when the server responds +- Undo functionality by storing the state history in a custom signal +- Caching the data in the `localStorage` for a fast first load. + +

+ Kitchen Sink Example +

+ # Contributing Contributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md) for more information. diff --git a/doc-assets/full-example.gif b/doc-assets/full-example.gif new file mode 100644 index 0000000..913d4c7 Binary files /dev/null and b/doc-assets/full-example.gif differ diff --git a/doc-assets/kitchen-sink.gif b/doc-assets/kitchen-sink.gif new file mode 100644 index 0000000..f00db2a Binary files /dev/null and b/doc-assets/kitchen-sink.gif differ diff --git a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.html b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.html index 3872b64..3aaca44 100644 --- a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.html +++ b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.html @@ -6,4 +6,4 @@
Contact Name: {contactInfo.contactName}
- + \ No newline at end of file diff --git a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js index f3277cc..622417a 100644 --- a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js +++ b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js @@ -10,4 +10,4 @@ export default class BusinessCard extends LightningElement { contactName: contactName.value }) ).value; -} +} \ No newline at end of file diff --git a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js-meta.xml b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js-meta.xml index 5e6eba0..acf1f27 100644 --- a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js-meta.xml +++ b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js-meta.xml @@ -8,4 +8,4 @@ lightningCommunity__Default lightningCommunity__Page - + \ No newline at end of file diff --git a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.html b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.html index 40160b0..83b1d89 100644 --- a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.html +++ b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.html @@ -10,4 +10,4 @@ value={contactName} onchange={handleContactNameChange} > - + \ No newline at end of file diff --git a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js index a801bc2..d948c82 100644 --- a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js +++ b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js @@ -13,4 +13,4 @@ export default class ContactInfoForm extends LightningElement { handleContactNameChange(event) { contactName.value = event.target.value; } -} +} \ No newline at end of file diff --git a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js-meta.xml b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js-meta.xml index 82f0043..953579a 100644 --- a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js-meta.xml +++ b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js-meta.xml @@ -8,4 +8,4 @@ lightningCommunity__Default lightningCommunity__Page - + \ No newline at end of file diff --git a/examples/counter/lwc/countChanger/countChanger.html b/examples/counter/lwc/countChanger/countChanger.html index 06b510b..77b37c1 100644 --- a/examples/counter/lwc/countChanger/countChanger.html +++ b/examples/counter/lwc/countChanger/countChanger.html @@ -3,4 +3,4 @@ - + \ No newline at end of file diff --git a/examples/counter/lwc/countChanger/countChanger.js b/examples/counter/lwc/countChanger/countChanger.js index 4b172b8..09a3099 100644 --- a/examples/counter/lwc/countChanger/countChanger.js +++ b/examples/counter/lwc/countChanger/countChanger.js @@ -9,4 +9,4 @@ export default class CountChanger extends LightningElement { decrementCount() { counter.value--; } -} +} \ No newline at end of file diff --git a/examples/counter/lwc/countChanger/countChanger.js-meta.xml b/examples/counter/lwc/countChanger/countChanger.js-meta.xml index 545a169..c5a6356 100644 --- a/examples/counter/lwc/countChanger/countChanger.js-meta.xml +++ b/examples/counter/lwc/countChanger/countChanger.js-meta.xml @@ -8,4 +8,4 @@ lightningCommunity__Default lightningCommunity__Page - + \ No newline at end of file diff --git a/examples/counter/lwc/countTracker/countTracker.html b/examples/counter/lwc/countTracker/countTracker.html index 44645ee..674de33 100644 --- a/examples/counter/lwc/countTracker/countTracker.html +++ b/examples/counter/lwc/countTracker/countTracker.html @@ -1,4 +1,4 @@ + \ No newline at end of file diff --git a/examples/counter/lwc/countTracker/countTracker.js b/examples/counter/lwc/countTracker/countTracker.js index db10620..6329155 100644 --- a/examples/counter/lwc/countTracker/countTracker.js +++ b/examples/counter/lwc/countTracker/countTracker.js @@ -8,4 +8,4 @@ export default class CountTracker extends LightningElement { counterPlusTwo = $computed(() => (this.counterPlusTwo = counterPlusTwo.value)) .value; -} +} \ No newline at end of file diff --git a/examples/counter/lwc/countTracker/countTracker.js-meta.xml b/examples/counter/lwc/countTracker/countTracker.js-meta.xml index 256254a..e01574c 100644 --- a/examples/counter/lwc/countTracker/countTracker.js-meta.xml +++ b/examples/counter/lwc/countTracker/countTracker.js-meta.xml @@ -8,4 +8,4 @@ lightningCommunity__Default lightningCommunity__Page - + \ No newline at end of file diff --git a/examples/demo-signals/lwc/demoSignals/apex-fetcher.js b/examples/demo-signals/lwc/demoSignals/apex-fetcher.js index 55b0f9c..598d6af 100644 --- a/examples/demo-signals/lwc/demoSignals/apex-fetcher.js +++ b/examples/demo-signals/lwc/demoSignals/apex-fetcher.js @@ -6,7 +6,7 @@ export const { data: fetchContacts } = $resource(getContacts); export const selectedAccountId = $signal(null); -$effect(() => console.log(selectedAccountId.value)); +$effect(() => console.log('selected Account Id', selectedAccountId.value)); export const { data: getAccount } = $resource(getAccountDetails, () => ({ accountId: selectedAccountId.value diff --git a/examples/demo-signals/lwc/demoSignals/contact-info.js b/examples/demo-signals/lwc/demoSignals/contact-info.js index ee69883..f51de82 100644 --- a/examples/demo-signals/lwc/demoSignals/contact-info.js +++ b/examples/demo-signals/lwc/demoSignals/contact-info.js @@ -2,4 +2,4 @@ import { $signal } from "c/signals"; export const accountName = $signal("ACME"); -export const contactName = $signal("John Doe"); +export const contactName = $signal("John Doe"); \ No newline at end of file diff --git a/examples/demo-signals/lwc/demoSignals/counter.js b/examples/demo-signals/lwc/demoSignals/counter.js index 9456e03..d1feb75 100644 --- a/examples/demo-signals/lwc/demoSignals/counter.js +++ b/examples/demo-signals/lwc/demoSignals/counter.js @@ -1,5 +1,15 @@ import { $signal, $effect, $computed, useLocalStorage } from "c/signals"; +// EXAMPLE OF DEFAULT COUNTER + +// export const counter = $signal(0); + +// EXAMPLE OF COUNTER USING LOCAL STORAGE + +export const counter = $signal(0, { + storage: useLocalStorage("counter") +}); + // EXAMPLE OF COUNTER USING COOKIES // let tomorrow = new Date(); @@ -9,13 +19,7 @@ import { $signal, $effect, $computed, useLocalStorage } from "c/signals"; // storage: useCookies("counter", tomorrow) // }); -// EXAMPLE OF COUNTER USING LOCAL STORAGE - -export const counter = $signal(0, { - storage: useLocalStorage("counter") -}); - $effect(() => console.log(counter.value)); export const counterPlusOne = $computed(() => counter.value + 1); -export const counterPlusTwo = $computed(() => counterPlusOne.value + 1); +export const counterPlusTwo = $computed(() => counterPlusOne.value + 1); \ No newline at end of file diff --git a/examples/demo-signals/lwc/demoSignals/demoSignals.js b/examples/demo-signals/lwc/demoSignals/demoSignals.js index ce35dd8..e1fac1d 100644 --- a/examples/demo-signals/lwc/demoSignals/demoSignals.js +++ b/examples/demo-signals/lwc/demoSignals/demoSignals.js @@ -1,3 +1,4 @@ export * from "./counter"; export * from "./contact-info"; export * from "./apex-fetcher"; +export * from "./shopping-cart"; diff --git a/examples/demo-signals/lwc/demoSignals/demoSignals.js-meta.xml b/examples/demo-signals/lwc/demoSignals/demoSignals.js-meta.xml index 175ca14..9cc72ec 100644 --- a/examples/demo-signals/lwc/demoSignals/demoSignals.js-meta.xml +++ b/examples/demo-signals/lwc/demoSignals/demoSignals.js-meta.xml @@ -4,4 +4,4 @@ Demo Signals false Demo Signals - + \ No newline at end of file diff --git a/examples/demo-signals/lwc/demoSignals/shopping-cart.js b/examples/demo-signals/lwc/demoSignals/shopping-cart.js new file mode 100644 index 0000000..eeeb16f --- /dev/null +++ b/examples/demo-signals/lwc/demoSignals/shopping-cart.js @@ -0,0 +1,80 @@ +import { $signal, $resource, $effect, useLocalStorage } from "c/signals"; +import getShoppingCart from "@salesforce/apex/ShoppingCartController.getShoppingCart"; +import updateShoppingCart from "@salesforce/apex/ShoppingCartController.updateShoppingCart"; + +/** + * @typedef {Object} ShoppingCart + * @property {Item[]} items + */ + +/** + * @typedef {Object} Item + * @property {string} id + * @property {string} name + * @property {string[]} properties + * @property {number} quantity + * @property {number} price + * @property {number} taxAmount + * @property {number} imgUrl + */ + +// Store each state change in the cart history +export const cartHistory = $signal([]); +let isUndoing = false; + +export const undoCartChange = () => { + isUndoing = true; + const lastState = cartHistory.value[cartHistory.value.length - 1]; + // Remove the last state from the history + cartHistory.value = cartHistory.value.slice(0, -1); + if (lastState) { + updateCart(lastState); + } + isUndoing = false; +}; + +/** + * Updates the cart on the server + * @param {ShoppingCart} newCart + * @param {ShoppingCart} previousValue + * @param mutate + */ +async function updateCartOnTheServer(newCart, previousValue, mutate) { + try { + // Keep track of the isUndoing value before making any async changes + // to ensure we don't update the history when undoing, even after + // an async operation. + const shouldUpdateHistory = !isUndoing; + // Update the cart on the server + const updatedShoppingCart = await updateShoppingCart({ + newItems: newCart.items + }); + + // Update the local state with the new cart received from the server + mutate(updatedShoppingCart); + + // Store the previous value in the history + if (shouldUpdateHistory) { + cartHistory.value = [...cartHistory.value, previousValue]; + } + } catch (error) { + mutate(null, error); + } +} + +const cachedCart = $signal(null, { + storage: useLocalStorage("shoppingCart") +}); + +export const { data: shoppingCart, mutate: updateCart } = $resource( + getShoppingCart, + {}, + { + initialValue: cachedCart.value, + onMutate: updateCartOnTheServer + } +); + +$effect(() => { + cachedCart.value = shoppingCart.value; +}); diff --git a/examples/main/default/audience/Default_examples.audience-meta.xml b/examples/main/default/audience/Default_examples.audience-meta.xml new file mode 100644 index 0000000..ca4c207 --- /dev/null +++ b/examples/main/default/audience/Default_examples.audience-meta.xml @@ -0,0 +1,8 @@ + + + Default + examples + + AllCriteriaMatch + true + diff --git a/examples/main/default/aura/forgotPassword/forgotPassword.cmp b/examples/main/default/aura/forgotPassword/forgotPassword.cmp new file mode 100644 index 0000000..4ad500e --- /dev/null +++ b/examples/main/default/aura/forgotPassword/forgotPassword.cmp @@ -0,0 +1,29 @@ + + + + + + + + + + + + +
+ +
+ +
+
+
+ + +
+ +
+ +
+ +
+
\ No newline at end of file diff --git a/examples/main/default/aura/forgotPassword/forgotPassword.cmp-meta.xml b/examples/main/default/aura/forgotPassword/forgotPassword.cmp-meta.xml new file mode 100644 index 0000000..b1f4b7a --- /dev/null +++ b/examples/main/default/aura/forgotPassword/forgotPassword.cmp-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Sample Component for forgotPassword + diff --git a/examples/main/default/aura/forgotPassword/forgotPassword.css b/examples/main/default/aura/forgotPassword/forgotPassword.css new file mode 100644 index 0000000..5d3bb22 --- /dev/null +++ b/examples/main/default/aura/forgotPassword/forgotPassword.css @@ -0,0 +1,76 @@ +.THIS #sfdc_username_container{ + margin-bottom:10px; + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS #sfdc_user{ + float:left; + width:23px; + height:25px; + padding-top:1px; + padding-left:2px; + margin:0px; + +} + +.THIS .login-icon { + color:#ccc;font-size:22px; +} + +.THIS button.sfdc_button { + width: 100%; + margin-top: 15px; + margin-bottom: 5px; + color: #fff; + background-color: #0070d2; + border-color: #357ebd; + display: inline-block; + text-align: center; + vertical-align: middle; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 10px 12px; + font-size: 16px; + font-family: 'Open Sans', sans-serif; + font-weight: 300; + line-height: 1.42857143; + border-radius: 2px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +.THIS button:hover { + background-color:#3276b1; + border-color:#285e8e; + cursor:pointer; +} + +.THIS input { + margin-left:10px; + margin-top: 3px; + border: 0px solid transparent; + width: 70%; + -webkit-appearance: none; + font-size: 14px; +} + +.THIS #error { + text-align: center; + color:#FF0000; +} + +.THIS a { + color:white; + text-decoration: none; +} +.THIS a:hover { + color:white; + text-decoration: none; +} \ No newline at end of file diff --git a/examples/main/default/aura/forgotPassword/forgotPassword.design b/examples/main/default/aura/forgotPassword/forgotPassword.design new file mode 100644 index 0000000..38ebfe8 --- /dev/null +++ b/examples/main/default/aura/forgotPassword/forgotPassword.design @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples/main/default/aura/forgotPassword/forgotPasswordController.js b/examples/main/default/aura/forgotPassword/forgotPasswordController.js new file mode 100644 index 0000000..a456ace --- /dev/null +++ b/examples/main/default/aura/forgotPassword/forgotPasswordController.js @@ -0,0 +1,23 @@ +({ + handleForgotPassword: function (component, event, helpler) { + helpler.handleForgotPassword(component, event, helpler); + }, + onKeyUp: function(component, event, helpler){ + //checks for "enter" key + if (event.getParam('keyCode')===13) { + helpler.handleForgotPassword(component, event, helpler); + } + }, + + setExpId: function (component, event, helper) { + var expId = event.getParam('expid'); + if (expId) { + component.set("v.expid", expId); + } + helper.setBrandingCookie(component, event, helper); + }, + + initialize: function(component, event, helper) { + $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap}).fire(); + } +}) \ No newline at end of file diff --git a/examples/main/default/aura/forgotPassword/forgotPasswordHelper.js b/examples/main/default/aura/forgotPassword/forgotPasswordHelper.js new file mode 100644 index 0000000..85467fb --- /dev/null +++ b/examples/main/default/aura/forgotPassword/forgotPasswordHelper.js @@ -0,0 +1,30 @@ +({ + qsToEventMap: { + 'expid' : 'e.c:setExpId' + }, + + handleForgotPassword: function (component, event, helpler) { + var username = component.find("username").get("v.value"); + var checkEmailUrl = component.get("v.checkEmailUrl"); + var action = component.get("c.forgotPassword"); + action.setParams({username:username, checkEmailUrl:checkEmailUrl}); + action.setCallback(this, function(a) { + var rtnValue = a.getReturnValue(); + if (rtnValue != null) { + component.set("v.errorMessage",rtnValue); + component.set("v.showError",true); + } + }); + $A.enqueueAction(action); + }, + + setBrandingCookie: function (component, event, helpler) { + var expId = component.get("v.expid"); + if (expId) { + var action = component.get("c.setExperienceId"); + action.setParams({expId:expId}); + action.setCallback(this, function(a){ }); + $A.enqueueAction(action); + } + } +}) \ No newline at end of file diff --git a/examples/main/default/aura/loginForm/loginForm.cmp b/examples/main/default/aura/loginForm/loginForm.cmp new file mode 100644 index 0000000..189eff4 --- /dev/null +++ b/examples/main/default/aura/loginForm/loginForm.cmp @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+
+
+
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+
\ No newline at end of file diff --git a/examples/main/default/aura/loginForm/loginForm.cmp-meta.xml b/examples/main/default/aura/loginForm/loginForm.cmp-meta.xml new file mode 100644 index 0000000..bcac32b --- /dev/null +++ b/examples/main/default/aura/loginForm/loginForm.cmp-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Sample Component for loginForm + diff --git a/examples/main/default/aura/loginForm/loginForm.css b/examples/main/default/aura/loginForm/loginForm.css new file mode 100644 index 0000000..5b06d29 --- /dev/null +++ b/examples/main/default/aura/loginForm/loginForm.css @@ -0,0 +1,103 @@ +.THIS #sfdc_username_container{ + margin-bottom:10px; + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS #sfdc_user{ + float:left; + width:23px; + height:25px; + padding-top:1px; + padding-left:2px; + margin:0px; + +} + +.THIS #sfdc_lock{ + float:left; + width:23px; + height:25px; + padding-top:1px; + padding-left:2px; + margin:0px; +} + +.THIS #error { + text-align: center; + color:#FF0000; +} + +.THIS .login-icon { + color:#ccc;font-size:22px; +} + +.THIS #sfdc_password_container{ + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS button.sfdc_button { + width: 100%; + margin-top: 15px; + margin-bottom: 5px; + color: #fff; + background-color: #0070d2; + border-color: #357ebd; + display: inline-block; + text-align: center; + vertical-align: middle; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 10px 12px; + font-size: 16px; + font-family: 'Open Sans', sans-serif; + font-weight: 300; + line-height: 1.42857143; + border-radius: 2px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +.THIS button:hover { + background-color:#3276b1; + border-color:#285e8e; + cursor:pointer; +} + +.THIS button .label { + color: #fff; +} + +.THIS input { + margin-left:10px; + margin-top: 3px; + border: 0px solid transparent; + width: 70%; + -webkit-appearance: none; + font-size: 14px; +} + + +.THIS a { + color:white; + text-decoration: none; +} +.THIS a:hover { + color:white; + text-decoration: none; +} + +.THIS label.uiLabel-hidden { + display:none; +} \ No newline at end of file diff --git a/examples/main/default/aura/loginForm/loginForm.design b/examples/main/default/aura/loginForm/loginForm.design new file mode 100644 index 0000000..0e53bfa --- /dev/null +++ b/examples/main/default/aura/loginForm/loginForm.design @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/examples/main/default/aura/loginForm/loginFormController.js b/examples/main/default/aura/loginForm/loginFormController.js new file mode 100644 index 0000000..d37160e --- /dev/null +++ b/examples/main/default/aura/loginForm/loginFormController.js @@ -0,0 +1,70 @@ +({ + initialize: function(component, event, helper) { + $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap}).fire(); + $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap2}).fire(); + component.set('v.isUsernamePasswordEnabled', helper.getIsUsernamePasswordEnabled(component, event, helper)); + component.set("v.isSelfRegistrationEnabled", helper.getIsSelfRegistrationEnabled(component, event, helper)); + component.set("v.communityForgotPasswordUrl", helper.getCommunityForgotPasswordUrl(component, event, helper)); + component.set("v.communitySelfRegisterUrl", helper.getCommunitySelfRegisterUrl(component, event, helper)); + }, + + handleLogin: function (component, event, helpler) { + helpler.handleLogin(component, event, helpler); + }, + + setStartUrl: function (component, event, helpler) { + var startUrl = event.getParam('startURL'); + if(startUrl) { + component.set("v.startUrl", startUrl); + } + }, + + setExpId: function (component, event, helper) { + var expId = event.getParam('expid'); + if (expId) { + component.set("v.expid", expId); + } + helper.setBrandingCookie(component, event, helper); + }, + + onKeyUp: function(component, event, helpler){ + //checks for "enter" key + if (event.getParam('keyCode')===13) { + helpler.handleLogin(component, event, helpler); + } + }, + + navigateToForgotPassword: function(cmp, event, helper) { + var forgotPwdUrl = cmp.get("v.communityForgotPasswordUrl"); + if ($A.util.isUndefinedOrNull(forgotPwdUrl)) { + forgotPwdUrl = cmp.get("v.forgotPasswordUrl"); + } + var startUrl = cmp.get("v.startUrl"); + if(startUrl){ + if(forgotPwdUrl.indexOf("?") === -1) { + forgotPwdUrl = forgotPwdUrl + '?startURL=' + decodeURIComponent(startUrl); + } else { + forgotPwdUrl = forgotPwdUrl + '&startURL=' + decodeURIComponent(startUrl); + } + } + var attributes = { url: forgotPwdUrl }; + $A.get("e.force:navigateToURL").setParams(attributes).fire(); + }, + + navigateToSelfRegister: function(cmp, event, helper) { + var selfRegUrl = cmp.get("v.communitySelfRegisterUrl"); + if (selfRegUrl == null) { + selfRegUrl = cmp.get("v.selfRegisterUrl"); + } + var startUrl = cmp.get("v.startUrl"); + if(startUrl){ + if(selfRegUrl.indexOf("?") === -1) { + selfRegUrl = selfRegUrl + '?startURL=' + decodeURIComponent(startUrl); + } else { + selfRegUrl = selfRegUrl + '&startURL=' + decodeURIComponent(startUrl); + } + } + var attributes = { url: selfRegUrl }; + $A.get("e.force:navigateToURL").setParams(attributes).fire(); + } +}) \ No newline at end of file diff --git a/examples/main/default/aura/loginForm/loginFormHelper.js b/examples/main/default/aura/loginForm/loginFormHelper.js new file mode 100644 index 0000000..5ec671c --- /dev/null +++ b/examples/main/default/aura/loginForm/loginFormHelper.js @@ -0,0 +1,83 @@ +({ + + qsToEventMap: { + 'startURL' : 'e.c:setStartUrl' + }, + + qsToEventMap2: { + 'expid' : 'e.c:setExpId' + }, + + handleLogin: function (component, event, helpler) { + var username = component.find("username").get("v.value"); + var password = component.find("password").get("v.value"); + var action = component.get("c.login"); + var startUrl = component.get("v.startUrl"); + + startUrl = decodeURIComponent(startUrl); + + action.setParams({username:username, password:password, startUrl:startUrl}); + action.setCallback(this, function(a){ + var rtnValue = a.getReturnValue(); + if (rtnValue !== null) { + component.set("v.errorMessage",rtnValue); + component.set("v.showError",true); + } + }); + $A.enqueueAction(action); + }, + + getIsUsernamePasswordEnabled : function (component, event, helpler) { + var action = component.get("c.getIsUsernamePasswordEnabled"); + action.setCallback(this, function(a){ + var rtnValue = a.getReturnValue(); + if (rtnValue !== null) { + component.set('v.isUsernamePasswordEnabled',rtnValue); + } + }); + $A.enqueueAction(action); + }, + + getIsSelfRegistrationEnabled : function (component, event, helpler) { + var action = component.get("c.getIsSelfRegistrationEnabled"); + action.setCallback(this, function(a){ + var rtnValue = a.getReturnValue(); + if (rtnValue !== null) { + component.set('v.isSelfRegistrationEnabled',rtnValue); + } + }); + $A.enqueueAction(action); + }, + + getCommunityForgotPasswordUrl : function (component, event, helpler) { + var action = component.get("c.getForgotPasswordUrl"); + action.setCallback(this, function(a){ + var rtnValue = a.getReturnValue(); + if (rtnValue !== null) { + component.set('v.communityForgotPasswordUrl',rtnValue); + } + }); + $A.enqueueAction(action); + }, + + getCommunitySelfRegisterUrl : function (component, event, helpler) { + var action = component.get("c.getSelfRegistrationUrl"); + action.setCallback(this, function(a){ + var rtnValue = a.getReturnValue(); + if (rtnValue !== null) { + component.set('v.communitySelfRegisterUrl',rtnValue); + } + }); + $A.enqueueAction(action); + }, + + setBrandingCookie: function (component, event, helpler) { + var expId = component.get("v.expid"); + if (expId) { + var action = component.get("c.setExperienceId"); + action.setParams({expId:expId}); + action.setCallback(this, function(a){ }); + $A.enqueueAction(action); + } + } +}) \ No newline at end of file diff --git a/examples/main/default/aura/selfRegister/selfRegister.cmp b/examples/main/default/aura/selfRegister/selfRegister.cmp new file mode 100644 index 0000000..5045e0d --- /dev/null +++ b/examples/main/default/aura/selfRegister/selfRegister.cmp @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+
+ + +
+ + +
+ +
+ + +
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/examples/main/default/aura/selfRegister/selfRegister.cmp-meta.xml b/examples/main/default/aura/selfRegister/selfRegister.cmp-meta.xml new file mode 100644 index 0000000..b9d5fb8 --- /dev/null +++ b/examples/main/default/aura/selfRegister/selfRegister.cmp-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Sample Component for selfRegister + diff --git a/examples/main/default/aura/selfRegister/selfRegister.css b/examples/main/default/aura/selfRegister/selfRegister.css new file mode 100644 index 0000000..2d45808 --- /dev/null +++ b/examples/main/default/aura/selfRegister/selfRegister.css @@ -0,0 +1,174 @@ +.THIS #sfdc_username_container{ + margin-bottom:10px; + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS #sfdc_nickname_container{ + margin-bottom:10px; + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS #sfdc_email_container{ + margin-bottom:10px; + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS #sfdc_extrafield_container{ + margin-bottom:10px; + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS #sfdc_user{ + float:left; + width:23px; + height:25px; + padding-top:1px; + padding-left:2px; + margin:0px; + +} + +.THIS #sfdc_lock{ + float:left; + width:23px; + height:25px; + padding-top:1px; + padding-left:2px; + margin:0px; +} + +.THIS .login-icon { + color:#ccc;font-size:22px; +} + +.THIS #sfdc_password_container{ + margin-bottom:10px; + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS #sfdc_confirm_password_container{ + padding: 12px; + background-color:white; + border: 1px solid #CCC; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.THIS button.sfdc_button { + width: 100%; + margin-top: 15px; + margin-bottom: 5px; + color: #fff; + background-color: #0070d2; + border-color: #357ebd; + display: inline-block; + text-align: center; + vertical-align: middle; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 10px 12px; + font-size: 16px; + font-family: 'Open Sans', sans-serif; + font-weight: 300; + line-height: 1.42857143; + border-radius: 2px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +.THIS button:hover { + background-color:#3276b1; + border-color:#285e8e; + cursor:pointer; +} + +.THIS input { + margin-left:10px; + margin-top: 3px; + border: 0px solid transparent; + width: 70%; + -webkit-appearance: none; + font-size: 14px; +} + +.THIS #sfdc_forgot{ + font-family: 'Open Sans', 'sans-serif'; + font-weight:300; + font-size: 14px; + margin-top: 10px +} + +.THIS #error { + text-align: center; + color:#FF0000; +} + +.THIS a { + color:white; + text-decoration: none; +} +.THIS a:hover { + color:white; + text-decoration: none; +} + +.THIS input[type="checkbox"] { + appearance: none; + border: 1px solid white; + height: 22px; + width: 22px; + vertical-align: middle; +} + +.THIS input[type="checkbox"]:checked { + border: 1px solid ; +} + +.THIS input[type="checkbox"]:checked:after { + display: block; + position: relative; + content: ''; + left: 3px; + top: 3px; + height: 6px; + width: 10px; + border-bottom: 4px solid /*#354452*/ white; + border-left: 4px solid /*#354452*/ white; + transform:rotate(-45deg); +} + +.THIS input[type="checkbox"].disabled { + opacity: 0.35; +} + +.THIS input[type="checkbox"] + label { + vertical-align: middle; +} \ No newline at end of file diff --git a/examples/main/default/aura/selfRegister/selfRegister.design b/examples/main/default/aura/selfRegister/selfRegister.design new file mode 100644 index 0000000..48c2743 --- /dev/null +++ b/examples/main/default/aura/selfRegister/selfRegister.design @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/main/default/aura/selfRegister/selfRegisterController.js b/examples/main/default/aura/selfRegister/selfRegisterController.js new file mode 100644 index 0000000..6981bb6 --- /dev/null +++ b/examples/main/default/aura/selfRegister/selfRegisterController.js @@ -0,0 +1,33 @@ +({ + initialize: function(component, event, helper) { + $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap}).fire(); + $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap2}).fire(); + component.set('v.extraFields', helper.getExtraFields(component, event, helper)); + }, + + handleSelfRegister: function (component, event, helpler) { + helpler.handleSelfRegister(component, event, helpler); + }, + + setStartUrl: function (component, event, helpler) { + var startUrl = event.getParam('startURL'); + if(startUrl) { + component.set("v.startUrl", startUrl); + } + }, + + setExpId: function (component, event, helper) { + var expId = event.getParam('expid'); + if (expId) { + component.set("v.expid", expId); + } + helper.setBrandingCookie(component, event, helper); + }, + + onKeyUp: function(component, event, helpler){ + //checks for "enter" key + if (event.getParam('keyCode')===13) { + helpler.handleSelfRegister(component, event, helpler); + } + } +}) \ No newline at end of file diff --git a/examples/main/default/aura/selfRegister/selfRegisterHelper.js b/examples/main/default/aura/selfRegister/selfRegisterHelper.js new file mode 100644 index 0000000..f12dd70 --- /dev/null +++ b/examples/main/default/aura/selfRegister/selfRegisterHelper.js @@ -0,0 +1,58 @@ +({ + qsToEventMap: { + 'startURL' : 'e.c:setStartUrl' + }, + + qsToEventMap2: { + 'expid' : 'e.c:setExpId' + }, + + handleSelfRegister: function (component, event, helpler) { + var accountId = component.get("v.accountId"); + var regConfirmUrl = component.get("v.regConfirmUrl"); + var firstname = component.find("firstname").get("v.value"); + var lastname = component.find("lastname").get("v.value"); + var email = component.find("email").get("v.value"); + var includePassword = component.get("v.includePasswordField"); + var password = component.find("password").get("v.value"); + var confirmPassword = component.find("confirmPassword").get("v.value"); + var action = component.get("c.selfRegister"); + var extraFields = JSON.stringify(component.get("v.extraFields")); // somehow apex controllers refuse to deal with list of maps + var startUrl = component.get("v.startUrl"); + + startUrl = decodeURIComponent(startUrl); + + action.setParams({firstname:firstname,lastname:lastname,email:email, + password:password, confirmPassword:confirmPassword, accountId:accountId, regConfirmUrl:regConfirmUrl, extraFields:extraFields, startUrl:startUrl, includePassword:includePassword}); + action.setCallback(this, function(a){ + var rtnValue = a.getReturnValue(); + if (rtnValue !== null) { + component.set("v.errorMessage",rtnValue); + component.set("v.showError",true); + } + }); + $A.enqueueAction(action); + }, + + getExtraFields : function (component, event, helpler) { + var action = component.get("c.getExtraFields"); + action.setParam("extraFieldsFieldSet", component.get("v.extraFieldsFieldSet")); + action.setCallback(this, function(a){ + var rtnValue = a.getReturnValue(); + if (rtnValue !== null) { + component.set('v.extraFields',rtnValue); + } + }); + $A.enqueueAction(action); + }, + + setBrandingCookie: function (component, event, helpler) { + var expId = component.get("v.expid"); + if (expId) { + var action = component.get("c.setExperienceId"); + action.setParams({expId:expId}); + action.setCallback(this, function(a){ }); + $A.enqueueAction(action); + } + } +}) \ No newline at end of file diff --git a/examples/main/default/aura/setExpId/setExpId.evt b/examples/main/default/aura/setExpId/setExpId.evt new file mode 100644 index 0000000..c1badd2 --- /dev/null +++ b/examples/main/default/aura/setExpId/setExpId.evt @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/examples/main/default/aura/setExpId/setExpId.evt-meta.xml b/examples/main/default/aura/setExpId/setExpId.evt-meta.xml new file mode 100644 index 0000000..515845f --- /dev/null +++ b/examples/main/default/aura/setExpId/setExpId.evt-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Sample Event for setExpId + diff --git a/examples/main/default/aura/setStartUrl/setStartUrl.evt b/examples/main/default/aura/setStartUrl/setStartUrl.evt new file mode 100644 index 0000000..7b0813b --- /dev/null +++ b/examples/main/default/aura/setStartUrl/setStartUrl.evt @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/examples/main/default/aura/setStartUrl/setStartUrl.evt-meta.xml b/examples/main/default/aura/setStartUrl/setStartUrl.evt-meta.xml new file mode 100644 index 0000000..e35c920 --- /dev/null +++ b/examples/main/default/aura/setStartUrl/setStartUrl.evt-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Sample Event for setStartUrl + diff --git a/examples/main/default/classes/ChangePasswordController.cls b/examples/main/default/classes/ChangePasswordController.cls new file mode 100644 index 0000000..31dde01 --- /dev/null +++ b/examples/main/default/classes/ChangePasswordController.cls @@ -0,0 +1,14 @@ +/** + * An apex page controller that exposes the change password functionality + */ +public with sharing class ChangePasswordController { + public String oldPassword {get; set;} + public String newPassword {get; set;} + public String verifyNewPassword {get; set;} + + public PageReference changePassword() { + return Site.changePassword(newPassword, verifyNewPassword, oldpassword); + } + + public ChangePasswordController() {} +} \ No newline at end of file diff --git a/examples/main/default/classes/ChangePasswordController.cls-meta.xml b/examples/main/default/classes/ChangePasswordController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/ChangePasswordController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/ChangePasswordControllerTest.cls b/examples/main/default/classes/ChangePasswordControllerTest.cls new file mode 100644 index 0000000..2814e42 --- /dev/null +++ b/examples/main/default/classes/ChangePasswordControllerTest.cls @@ -0,0 +1,14 @@ +/** + * An apex page controller that exposes the change password functionality + */ +@IsTest public with sharing class ChangePasswordControllerTest { + @IsTest(SeeAllData=true) public static void testChangePasswordController() { + // Instantiate a new controller with all parameters in the page + ChangePasswordController controller = new ChangePasswordController(); + controller.oldPassword = '123456'; + controller.newPassword = 'qwerty1'; + controller.verifyNewPassword = 'qwerty1'; + + System.assertEquals(controller.changePassword(),null); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/ChangePasswordControllerTest.cls-meta.xml b/examples/main/default/classes/ChangePasswordControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/ChangePasswordControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/CommunitiesLandingController.cls b/examples/main/default/classes/CommunitiesLandingController.cls new file mode 100644 index 0000000..9633c5a --- /dev/null +++ b/examples/main/default/classes/CommunitiesLandingController.cls @@ -0,0 +1,12 @@ +/** + * An apex page controller that takes the user to the right start page based on credentials or lack thereof + */ +public with sharing class CommunitiesLandingController { + + // Code we will invoke on page load. + public PageReference forwardToStartPage() { + return Network.communitiesLanding(); + } + + public CommunitiesLandingController() {} +} \ No newline at end of file diff --git a/examples/main/default/classes/CommunitiesLandingController.cls-meta.xml b/examples/main/default/classes/CommunitiesLandingController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/CommunitiesLandingController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/CommunitiesLandingControllerTest.cls b/examples/main/default/classes/CommunitiesLandingControllerTest.cls new file mode 100644 index 0000000..84b5643 --- /dev/null +++ b/examples/main/default/classes/CommunitiesLandingControllerTest.cls @@ -0,0 +1,18 @@ +/** + * An apex page controller that takes the user to the right start page based on credentials or lack thereof + */ +@IsTest public with sharing class CommunitiesLandingControllerTest { + @IsTest(SeeAllData=true) public static void testCommunitiesLandingController() { + // Instantiate a new controller with all parameters in the page + CommunitiesLandingController controller = new CommunitiesLandingController(); + PageReference pageRef = controller.forwardToStartPage(); + //PageRef is either null or an empty object in test context + if(pageRef != null){ + String url = pageRef.getUrl(); + if(url != null){ + System.assertEquals(true, String.isEmpty(url)); + //show up in perforce + } + } + } +} \ No newline at end of file diff --git a/examples/main/default/classes/CommunitiesLandingControllerTest.cls-meta.xml b/examples/main/default/classes/CommunitiesLandingControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/CommunitiesLandingControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/CommunitiesLoginController.cls b/examples/main/default/classes/CommunitiesLoginController.cls new file mode 100644 index 0000000..35ab098 --- /dev/null +++ b/examples/main/default/classes/CommunitiesLoginController.cls @@ -0,0 +1,14 @@ +/** + * An apex page controller that exposes the site login functionality + */ +global with sharing class CommunitiesLoginController { + + global CommunitiesLoginController () {} + + // Code we will invoke on page load. + global PageReference forwardToAuthPage() { + String startUrl = System.currentPageReference().getParameters().get('startURL'); + String displayType = System.currentPageReference().getParameters().get('display'); + return Network.forwardToAuthPage(startUrl, displayType); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/CommunitiesLoginController.cls-meta.xml b/examples/main/default/classes/CommunitiesLoginController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/CommunitiesLoginController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/CommunitiesLoginControllerTest.cls b/examples/main/default/classes/CommunitiesLoginControllerTest.cls new file mode 100644 index 0000000..b892fdc --- /dev/null +++ b/examples/main/default/classes/CommunitiesLoginControllerTest.cls @@ -0,0 +1,10 @@ +/** + * An apex page controller that exposes the site login functionality + */ +@IsTest global with sharing class CommunitiesLoginControllerTest { + @IsTest(SeeAllData=true) + global static void testCommunitiesLoginController () { + CommunitiesLoginController controller = new CommunitiesLoginController(); + System.assertEquals(null, controller.forwardToAuthPage()); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/CommunitiesLoginControllerTest.cls-meta.xml b/examples/main/default/classes/CommunitiesLoginControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/CommunitiesLoginControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls b/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls new file mode 100644 index 0000000..d98d1fe --- /dev/null +++ b/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls @@ -0,0 +1,7 @@ +/** + * An apex page controller that takes the user to the right start page based on credentials or lack thereof + */ +public with sharing class CommunitiesSelfRegConfirmController { + + public CommunitiesSelfRegConfirmController() {} +} \ No newline at end of file diff --git a/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls-meta.xml b/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls b/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls new file mode 100644 index 0000000..18a60bb --- /dev/null +++ b/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls @@ -0,0 +1,9 @@ +/** + * An apex page controller that takes the user to the right start page based on credentials or lack thereof + */ +@IsTest public with sharing class CommunitiesSelfRegConfirmControllerTest { + @IsTest(SeeAllData=true) public static void testCommunitiesSelfRegConfirmController() { + // Instantiate a new controller with all parameters in the page + CommunitiesSelfRegConfirmController controller = new CommunitiesSelfRegConfirmController(); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls-meta.xml b/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/CommunitiesSelfRegController.cls b/examples/main/default/classes/CommunitiesSelfRegController.cls new file mode 100644 index 0000000..3b738aa --- /dev/null +++ b/examples/main/default/classes/CommunitiesSelfRegController.cls @@ -0,0 +1,74 @@ +/** + * An apex page controller that supports self registration of users in communities that allow self registration + */ +public class CommunitiesSelfRegController { + + public String firstName {get; set;} + public String lastName {get; set;} + public String email {get; set;} + public String password {get; set {password = value == null ? value : value.trim(); } } + public String confirmPassword {get; set { confirmPassword = value == null ? value : value.trim(); } } + public String communityNickname {get; set { communityNickname = value == null ? value : value.trim(); } } + + public CommunitiesSelfRegController() { + String expid = ApexPages.currentPage().getParameters().get('expid'); + if (expId != null) { + Site.setExperienceId(expId); + } + } + + private boolean isValidPassword() { + return password == confirmPassword; + } + + public PageReference registerUser() { + + // it's okay if password is null - we'll send the user a random password in that case + if (!isValidPassword()) { + ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, Label.site.passwords_dont_match); + ApexPages.addMessage(msg); + return null; + } + + String profileId = null; // To be filled in by customer. + String roleEnum = null; // To be filled in by customer. + String accountId = ''; // To be filled in by customer. + + String userName = email; + + User u = new User(); + u.Username = userName; + u.Email = email; + u.FirstName = firstName; + u.LastName = lastName; + u.CommunityNickname = communityNickname; + u.ProfileId = profileId; + + String userId; + + try { + userId = Site.createExternalUser(u, accountId, password); + } catch(Site.ExternalUserCreateException ex) { + List errors = ex.getDisplayMessages(); + for (String error : errors) { + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, error)); + } + + // This message is used for debugging. Do not display this in the UI to the end user. + // It has the information around why the user creation failed. + System.debug(ex.getMessage()); + } + + if (userId != null) { + if (password != null && password.length() > 1) { + return Site.login(userName, password, ApexPages.currentPage().getParameters().get('startURL')); + } + else { + PageReference page = System.Page.CommunitiesSelfRegConfirm; + page.setRedirect(true); + return page; + } + } + return null; + } +} \ No newline at end of file diff --git a/examples/main/default/classes/CommunitiesSelfRegController.cls-meta.xml b/examples/main/default/classes/CommunitiesSelfRegController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/CommunitiesSelfRegController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls b/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls new file mode 100644 index 0000000..bda9c05 --- /dev/null +++ b/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls @@ -0,0 +1,20 @@ +/** + * An apex page controller that supports self registration of users in communities that allow self registration + */ +@IsTest public with sharing class CommunitiesSelfRegControllerTest { + @IsTest(SeeAllData=true) + public static void testCommunitiesSelfRegController() { + CommunitiesSelfRegController controller = new CommunitiesSelfRegController(); + controller.firstName = 'FirstName'; + controller.lastName = 'LastName'; + controller.email = 'test@force.com'; + controller.communityNickname = 'test'; + + // registerUser will always return null when the page isn't accessed as a guest user + System.assert(controller.registerUser() == null); + + controller.password = 'abcd1234'; + controller.confirmPassword = 'abcd123'; + System.assert(controller.registerUser() == null); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls-meta.xml b/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/ForgotPasswordController.cls b/examples/main/default/classes/ForgotPasswordController.cls new file mode 100644 index 0000000..480df8b --- /dev/null +++ b/examples/main/default/classes/ForgotPasswordController.cls @@ -0,0 +1,19 @@ +/** + * An apex page controller that exposes the site forgot password functionality + */ +public with sharing class ForgotPasswordController { + public String username {get; set;} + + public ForgotPasswordController() {} + + public PageReference forgotPassword() { + boolean success = Site.forgotPassword(username); + PageReference pr = Page.ForgotPasswordConfirm; + pr.setRedirect(true); + + if (success) { + return pr; + } + return null; + } +} \ No newline at end of file diff --git a/examples/main/default/classes/ForgotPasswordController.cls-meta.xml b/examples/main/default/classes/ForgotPasswordController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/ForgotPasswordController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/ForgotPasswordControllerTest.cls b/examples/main/default/classes/ForgotPasswordControllerTest.cls new file mode 100644 index 0000000..1712f2c --- /dev/null +++ b/examples/main/default/classes/ForgotPasswordControllerTest.cls @@ -0,0 +1,12 @@ +/** + * An apex page controller that exposes the site forgot password functionality + */ +@IsTest public with sharing class ForgotPasswordControllerTest { + @IsTest(SeeAllData=true) public static void testForgotPasswordController() { + // Instantiate a new controller with all parameters in the page + ForgotPasswordController controller = new ForgotPasswordController(); + controller.username = 'test@salesforce.com'; + + System.assertEquals(controller.forgotPassword(),null); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/ForgotPasswordControllerTest.cls-meta.xml b/examples/main/default/classes/ForgotPasswordControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/ForgotPasswordControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/LightningForgotPasswordController.cls b/examples/main/default/classes/LightningForgotPasswordController.cls new file mode 100644 index 0000000..2a21187 --- /dev/null +++ b/examples/main/default/classes/LightningForgotPasswordController.cls @@ -0,0 +1,35 @@ +global class LightningForgotPasswordController { + + public LightningForgotPasswordController() { + + } + + @AuraEnabled + public static String forgotPassword(String username, String checkEmailUrl) { + try { + Site.forgotPassword(username); + ApexPages.PageReference checkEmailRef = new PageReference(checkEmailUrl); + if(!Site.isValidUsername(username)) { + return Label.Site.invalid_email; + } + aura.redirect(checkEmailRef); + return null; + } + catch (Exception ex) { + return ex.getMessage(); + } + } + + @AuraEnabled + global static String setExperienceId(String expId) { + // Return null if there is no error, else it will return the error message + try { + if (expId != null) { + Site.setExperienceId(expId); + } + return null; + } catch (Exception ex) { + return ex.getMessage(); + } + } +} \ No newline at end of file diff --git a/examples/main/default/classes/LightningForgotPasswordController.cls-meta.xml b/examples/main/default/classes/LightningForgotPasswordController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/LightningForgotPasswordController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/LightningForgotPasswordControllerTest.cls b/examples/main/default/classes/LightningForgotPasswordControllerTest.cls new file mode 100644 index 0000000..ab49bb8 --- /dev/null +++ b/examples/main/default/classes/LightningForgotPasswordControllerTest.cls @@ -0,0 +1,25 @@ +@IsTest(SeeAllData = true) +public with sharing class LightningForgotPasswordControllerTest { + + /* Verifies that ForgotPasswordController handles invalid usernames appropriately */ + @IsTest + static void testLightningForgotPasswordControllerInvalidUserName() { + System.assertEquals(LightningForgotPasswordController.forgotPassword('fakeUser', 'http://a.com'), Label.Site.invalid_email); + System.assertEquals(LightningForgotPasswordController.forgotPassword(null, 'http://a.com'), Label.Site.invalid_email); + System.assertEquals(LightningForgotPasswordController.forgotPassword('a', '/home/home.jsp'), Label.Site.invalid_email); + } + + /* Verifies that null checkEmailRef url throws proper exception. */ + @IsTest + static void testLightningForgotPasswordControllerWithNullCheckEmailRef() { + System.assertEquals(LightningForgotPasswordController.forgotPassword('a', null), 'Argument 1 cannot be null'); + System.assertEquals(LightningForgotPasswordController.forgotPassword('a@salesforce.com', null), 'Argument 1 cannot be null'); + } + + /* Verifies that LightningForgotPasswordController object is instantiated correctly. */ + @IsTest + static void LightningForgotPasswordControllerInstantiation() { + LightningForgotPasswordController controller = new LightningForgotPasswordController(); + System.assertNotEquals(controller, null); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/LightningForgotPasswordControllerTest.cls-meta.xml b/examples/main/default/classes/LightningForgotPasswordControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/LightningForgotPasswordControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/LightningLoginFormController.cls b/examples/main/default/classes/LightningLoginFormController.cls new file mode 100644 index 0000000..3c77267 --- /dev/null +++ b/examples/main/default/classes/LightningLoginFormController.cls @@ -0,0 +1,65 @@ +global class LightningLoginFormController { + + public LightningLoginFormController() { + + } + + @AuraEnabled + public static String login(String username, String password, String startUrl) { + try{ + ApexPages.PageReference lgn = Site.login(username, password, startUrl); + aura.redirect(lgn); + return null; + } + catch (Exception ex) { + return ex.getMessage(); + } + } + + @AuraEnabled + public static Boolean getIsUsernamePasswordEnabled() { + Auth.AuthConfiguration authConfig = getAuthConfig(); + return authConfig.getUsernamePasswordEnabled(); + } + + @AuraEnabled + public static Boolean getIsSelfRegistrationEnabled() { + Auth.AuthConfiguration authConfig = getAuthConfig(); + return authConfig.getSelfRegistrationEnabled(); + } + + @AuraEnabled + public static String getSelfRegistrationUrl() { + Auth.AuthConfiguration authConfig = getAuthConfig(); + if (authConfig.getSelfRegistrationEnabled()) { + return authConfig.getSelfRegistrationUrl(); + } + return null; + } + + @AuraEnabled + public static String getForgotPasswordUrl() { + Auth.AuthConfiguration authConfig = getAuthConfig(); + return authConfig.getForgotPasswordUrl(); + } + + @TestVisible + private static Auth.AuthConfiguration getAuthConfig(){ + Id networkId = Network.getNetworkId(); + Auth.AuthConfiguration authConfig = new Auth.AuthConfiguration(networkId,''); + return authConfig; + } + + @AuraEnabled + global static String setExperienceId(String expId) { + // Return null if there is no error, else it will return the error message + try { + if (expId != null) { + Site.setExperienceId(expId); + } + return null; + } catch (Exception ex) { + return ex.getMessage(); + } + } +} \ No newline at end of file diff --git a/examples/main/default/classes/LightningLoginFormController.cls-meta.xml b/examples/main/default/classes/LightningLoginFormController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/LightningLoginFormController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/LightningLoginFormControllerTest.cls b/examples/main/default/classes/LightningLoginFormControllerTest.cls new file mode 100644 index 0000000..b008372 --- /dev/null +++ b/examples/main/default/classes/LightningLoginFormControllerTest.cls @@ -0,0 +1,30 @@ +@IsTest(SeeAllData = true) +public with sharing class LightningLoginFormControllerTest { + + @IsTest + static void LightningLoginFormControllerInstantiation() { + LightningLoginFormController controller = new LightningLoginFormController(); + System.assertNotEquals(controller, null); + } + + @IsTest + static void testIsUsernamePasswordEnabled() { + System.assertEquals(true, LightningLoginFormController.getIsUsernamePasswordEnabled()); + } + + @IsTest + static void testIsSelfRegistrationEnabled() { + System.assertEquals(false, LightningLoginFormController.getIsSelfRegistrationEnabled()); + } + + @IsTest + static void testGetSelfRegistrationURL() { + System.assertEquals(null, LightningLoginFormController.getSelfRegistrationUrl()); + } + + @IsTest + static void testAuthConfig() { + Auth.AuthConfiguration authConfig = LightningLoginFormController.getAuthConfig(); + System.assertNotEquals(null, authConfig); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/LightningLoginFormControllerTest.cls-meta.xml b/examples/main/default/classes/LightningLoginFormControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/LightningLoginFormControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/LightningSelfRegisterController.cls b/examples/main/default/classes/LightningSelfRegisterController.cls new file mode 100644 index 0000000..b284918 --- /dev/null +++ b/examples/main/default/classes/LightningSelfRegisterController.cls @@ -0,0 +1,137 @@ +global class LightningSelfRegisterController { + + public LightningSelfRegisterController() { + + } + + @TestVisible + private static boolean isValidPassword(String password, String confirmPassword) { + return password == confirmPassword; + } + + @TestVisible + private static boolean siteAsContainerEnabled(String communityUrl) { + Auth.AuthConfiguration authConfig = new Auth.AuthConfiguration(communityUrl,''); + return authConfig.isCommunityUsingSiteAsContainer(); + } + + @TestVisible + private static void validatePassword(User u, String password, String confirmPassword) { + if(!Test.isRunningTest()) { + Site.validatePassword(u, password, confirmPassword); + } + return; + } + + @AuraEnabled + public static String selfRegister(String firstname ,String lastname, String email, String password, String confirmPassword, String accountId, String regConfirmUrl, String extraFields, String startUrl, Boolean includePassword) { + Savepoint sp = null; + try { + sp = Database.setSavepoint(); + + if (lastname == null || String.isEmpty(lastname)) { + return Label.Site.lastname_is_required; + } + + if (email == null || String.isEmpty(email)) { + return Label.Site.email_is_required; + } + + User u = new User(); + u.Username = email; + u.put('Email',email); + + u.FirstName = firstname; + u.LastName = lastname; + + String networkId = Network.getNetworkId(); + + // If using site to host the community the user should not hit s1 after logging in from mobile. + if(networkId != null && siteAsContainerEnabled(Network.getLoginUrl(networkId))) { + u.put('UserPreferencesHideS1BrowserUI',true); + } + + String nickname = ((firstname != null && firstname.length() > 0) ? firstname.substring(0,1) : '' ) + lastname.substring(0,1); + nickname += String.valueOf(Crypto.getRandomInteger()).substring(1,7); + u.put('CommunityNickname', nickname); + + if (extraFields != null) { + List extraFieldsList = (List) JSON.deserializeUntyped(extraFields); + for (Object thisFieldObject : extraFieldsList) { + Map thisField = (Map) thisFieldObject; + Schema.SObjectField sof = Schema.SObjectType.User.fields.getMap().get((String) thisField.get('fieldPath')); + u.put(sof, thisField.get('value')); + } + } + + if (includePassword) { + if (!isValidPassword(password, confirmPassword)) { + return Label.site.passwords_dont_match; + } + validatePassword(u, password, confirmPassword); + } + else { + password = null; + } + + // lastName is a required field on user, but if it isn't specified, we'll default it to the username + String userId = Site.createPortalUser(u, accountId, password); + // create a fake userId for test. + if (Test.isRunningTest()) { + userId = 'fakeUserId'; + } + if (userId != null) { + if (password != null && password.length() > 1) { + ApexPages.PageReference lgn = Site.login(email, password, startUrl); + if(!Test.isRunningTest()) { + aura.redirect(lgn); + } + } + else { + ApexPages.PageReference confirmRef = new PageReference(regConfirmUrl); + if(!Test.isRunningTest()) { + aura.redirect(confirmRef); + } + + } + } + return null; + } + catch (Exception ex) { + Database.rollback(sp); + return ex.getMessage(); + } + } + + @AuraEnabled + public static List> getExtraFields(String extraFieldsFieldSet) { + List> extraFields = new List>(); + Schema.FieldSet fieldSet = Schema.SObjectType.User.fieldSets.getMap().get(extraFieldsFieldSet); + if(!Test.isRunningTest()) { + if (fieldSet != null) { + for (Schema.FieldSetMember f : fieldSet.getFields()) { + Map fieldDetail = new Map(); + fieldDetail.put('dbRequired', f.getDBRequired()); + fieldDetail.put('fieldPath', f.getFieldPath()); + fieldDetail.put('label', f.getLabel()); + fieldDetail.put('required', f.getRequired()); + fieldDetail.put('type', f.getType()); + fieldDetail.put('value', ''); // client will populate + extraFields.add(fieldDetail); + }}} + return extraFields; + } + + @AuraEnabled + global static String setExperienceId(String expId) { + // Return null if there is no error, else it will return the error message + try { + if (expId != null) { + Site.setExperienceId(expId); + } + return null; + } catch (Exception ex) { + return ex.getMessage(); + } + } +} \ No newline at end of file diff --git a/examples/main/default/classes/LightningSelfRegisterController.cls-meta.xml b/examples/main/default/classes/LightningSelfRegisterController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/LightningSelfRegisterController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/LightningSelfRegisterControllerTest.cls b/examples/main/default/classes/LightningSelfRegisterControllerTest.cls new file mode 100644 index 0000000..5dec8d9 --- /dev/null +++ b/examples/main/default/classes/LightningSelfRegisterControllerTest.cls @@ -0,0 +1,102 @@ +@IsTest(SeeAllData = true) +public with sharing class LightningSelfRegisterControllerTest { + + /* Verifies that IsValidPassword method with various password combinations. */ + @IsTest + static void testIsValidPassword() { + System.assert(LightningSelfRegisterController.isValidPassword('password?@12334', 'password?@12334') == true); + System.assert(LightningSelfRegisterController.isValidPassword('password?@12334', 'dummyPassword') == false); + System.assert(LightningSelfRegisterController.isValidPassword('password?@12334', null) == false); + System.assert(LightningSelfRegisterController.isValidPassword(null, 'fakePwd') == false); + } + + @IsTest + static void testSiteAsContainerEnabled() { + System.assertNotEquals(null, LightningSelfRegisterController.siteAsContainerEnabled('https://portaleu1-developer-edition.eu11.force.com')); + } + + /* Verifies the selfRegistration method flow with various invalid inputs */ + @IsTest + static void testSelfRegistration() { + Map < String, String > paramsMap = initializeParams(); + System.assertNotEquals(null, paramsMap); + System.assertEquals(Label.Site.lastname_is_required, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), '', paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true)); + System.assertEquals(Label.Site.email_is_required, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), '', paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true)); + System.assertEquals(Label.Site.email_is_required, LightningSelfRegisterController.selfRegister(null, paramsMap.get('lastName'), '', null, paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true)); + System.assertEquals(Label.site.passwords_dont_match, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordWrong'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true)); + System.assertNotEquals(null, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), '', paramsMap.get('password'), paramsMap.get('confirmPasswordWrong'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), false)); + } + + + /* Verifies the selfRegistration flow for valid inputs */ + @IsTest + static void testSelfRegisterWithProperCredentials() { + Map < String, String > paramsMap = initializeParams(); + System.assertEquals(null, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true)); + } + + /* Verifies SelfRegistration flow with an accounId that is created within the test */ + @IsTest + static void testSelfRegisterWithCreatedAccount() { + Account acc = new Account(name = 'test acc'); + insert acc; + List < Account > accounts = [SELECT Id FROM Account LIMIT 1]; + System.assert(!accounts.isEmpty(), 'There must be at least one account in this environment!'); + String accountId = accounts[0].Id; + Map < String, String > paramsMap = initializeParams(); + System.assertEquals(null, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), accountId, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), false)); + } + + @IsTest + static void testGetNullExtraFields() { + System.assertEquals(new List < Map < String, Object >> (), LightningSelfRegisterController.getExtraFields(null)); + } + + @IsTest + static void testGetNonEmptyExtraFields() { + System.assertEquals(new List < Map < String, Object >> (), LightningSelfRegisterController.getExtraFields('field1')); + } + + /* Verifies validation of extraFields within the Self Registration flow */ + @IsTest + static void testGetExtraFieldsInSelfRegistration() { + List < Map < String, Object >> fieldlist = new List < Map < String, Object >> (); + Map < String, String > paramsMap = initializeParams(); + Map < String, Object > fieldMap = new Map < String, Object > (); + fieldMap.put('description', 'new field'); + fieldMap.put('fieldPath', 'dummyPath'); + fieldlist.add(fieldMap); + String extraFields = JSON.serialize(fieldlist); + System.assertNotEquals(null, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), extraFields, paramsMap.get('startUrl'), true)); + } + + @IsTest + static void LightningSelfRegisterControllerInstantiation() { + LightningSelfRegisterController controller = new LightningSelfRegisterController(); + System.assertNotEquals(controller, null); + } + + /* Helper method to initialize the parameters required for SelfRegistration. */ + private static Map < String, String > initializeParams() { + Map < String, String > paramsMap = new Map < String, String > (); + String firstName = 'test'; + String lastName = 'User'; + String email = 'testUser@salesforce.com'; + String password = 'testuser123'; + String confirmPasswordCorrect = 'testuser123'; + String confirmPasswordWrong = 'wrongpassword'; + String accountId = 'testuser123'; + String regConfirmUrl = 'http://registration-confirm.com'; + String startUrl = 'http://my.company.salesforce.com'; + paramsMap.put('firstName', firstName); + paramsMap.put('lastName', lastName); + paramsMap.put('email', email); + paramsMap.put('password', password); + paramsMap.put('confirmPasswordCorrect', confirmPasswordCorrect); + paramsMap.put('confirmPasswordWrong', confirmPasswordWrong); + paramsMap.put('accountId', accountId); + paramsMap.put('regConfirmUrl', regConfirmUrl); + paramsMap.put('startUrl', startUrl); + return paramsMap; + } +} \ No newline at end of file diff --git a/examples/main/default/classes/LightningSelfRegisterControllerTest.cls-meta.xml b/examples/main/default/classes/LightningSelfRegisterControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/LightningSelfRegisterControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/MicrobatchSelfRegController.cls b/examples/main/default/classes/MicrobatchSelfRegController.cls new file mode 100644 index 0000000..e848951 --- /dev/null +++ b/examples/main/default/classes/MicrobatchSelfRegController.cls @@ -0,0 +1,57 @@ +public class MicrobatchSelfRegController { + public String firstName {get; set;} + public String lastName {get; set;} + public String email {get; set;} + public String communityNickname {get; set { communityNickname = value == null ? value : value.trim(); } } + + public MicrobatchSelfRegController() { + String expid = ApexPages.currentPage().getParameters().get('expid'); + if (expId != null) { + Site.setExperienceId(expId); + } + } + + public PageReference registerUser() { + String userName = email; + String accountName; // to be filled by customer + String contactName; //to be filled by customer + String profileId = null; //to be filled by customer + String UUID; + + User u = new User(); + u.Username = userName; + u.Email = email; + u.FirstName = firstName; + u.LastName = lastName; + u.CommunityNickname = communityNickname; + u.ProfileId = profileId; + u.LocaleSidKey = 'en_US'; + u.TimeZoneSidKey = 'GMT'; + u.LanguageLocaleKey = 'en_US'; + u.EmailEncodingKey = 'UTF-8'; + + Account acc = new Account(); + acc.Name = 'Account for ' + lastName; + Contact c = new Contact(); + c.lastName = lastName; + + try { + UUID = Network.createExternalUserAsync(u, c,acc); + } catch(Site.ExternalUserCreateException ex) { + List errors = ex.getDisplayMessages(); + for (String error : errors) { + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, error)); + } + + // This message is used for debugging. Do not display this in the UI to the end user. + // It has the information around why the user creation failed. + System.debug(ex.getMessage()); + } + if (UUID != null) { + PageReference page = System.Page.CommunitiesSelfRegConfirm; + page.setRedirect(true); + return page; + } + return null; + } +} \ No newline at end of file diff --git a/examples/main/default/classes/MicrobatchSelfRegController.cls-meta.xml b/examples/main/default/classes/MicrobatchSelfRegController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/MicrobatchSelfRegController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls b/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls new file mode 100644 index 0000000..b6b8365 --- /dev/null +++ b/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls @@ -0,0 +1,14 @@ +@IsTest + public with sharing class MicrobatchSelfRegControllerTest { + @IsTest(SeeAllData=true) + public static void testMicrobatchSelfRegController() { + MicrobatchSelfRegController controller = new MicrobatchSelfRegController(); + controller.firstName = 'FirstName'; + controller.lastName = 'LastName'; + controller.email = 'test@force.com'; + controller.communityNickname = 'test'; + + // registerUser will always return null when the page isn't accessed as a guest user + System.assert(controller.registerUser() == null); + } + } \ No newline at end of file diff --git a/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls-meta.xml b/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/MyProfilePageController.cls b/examples/main/default/classes/MyProfilePageController.cls new file mode 100644 index 0000000..f3fe255 --- /dev/null +++ b/examples/main/default/classes/MyProfilePageController.cls @@ -0,0 +1,53 @@ +/** + * An apex class that updates portal user details. + Guest users are never able to access this page. + */ +public with sharing class MyProfilePageController { + + private User user; + private boolean isEdit = false; + + public User getUser() { + return user; + } + + public MyProfilePageController() { + user = [SELECT id, email, username, usertype, communitynickname, timezonesidkey, languagelocalekey, firstname, lastname, phone, title, + street, city, country, postalcode, state, localesidkey, mobilephone, extension, fax, contact.email + FROM User + WHERE id = :UserInfo.getUserId()]; + // guest users should never be able to access this page + if (user.usertype == 'GUEST') { + throw new NoAccessException(); + } + } + + public Boolean getIsEdit() { + return isEdit; + } + + public void edit() { + isEdit=true; + } + + public void save() { + try { + update user; + isEdit=false; + } catch(DmlException e) { + ApexPages.addMessages(e); + } + } + + public PageReference changePassword() { + return Page.ChangePassword; + } + + public void cancel() { + isEdit=false; + user = [SELECT id, email, username, communitynickname, timezonesidkey, languagelocalekey, firstname, lastname, phone, title, + street, city, country, postalcode, state, localesidkey, mobilephone, extension, fax, contact.email + FROM User + WHERE id = :UserInfo.getUserId()]; + } +} \ No newline at end of file diff --git a/examples/main/default/classes/MyProfilePageController.cls-meta.xml b/examples/main/default/classes/MyProfilePageController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/MyProfilePageController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/MyProfilePageControllerTest.cls b/examples/main/default/classes/MyProfilePageControllerTest.cls new file mode 100644 index 0000000..1b59342 --- /dev/null +++ b/examples/main/default/classes/MyProfilePageControllerTest.cls @@ -0,0 +1,55 @@ +/** + * An apex class that updates details of a portal user. + Guest users are never able to access this page. + */ +@IsTest public with sharing class MyProfilePageControllerTest { + + @IsTest(SeeAllData=true) static void testSave() { + // Modify the test to query for a portal user that exists in your org + List existingPortalUsers = [SELECT id, profileId, userRoleId FROM User WHERE UserRoleId <> null AND UserType='CustomerSuccess']; + + if (existingPortalUsers.isEmpty()) { + User currentUser = [select id, title, firstname, lastname, email, phone, mobilephone, fax, street, city, state, postalcode, country + FROM User WHERE id =: UserInfo.getUserId()]; + MyProfilePageController controller = new MyProfilePageController(); + System.assertEquals(currentUser.Id, controller.getUser().Id, 'Did not successfully load the current user'); + System.assert(controller.getIsEdit() == false, 'isEdit should default to false'); + controller.edit(); + System.assert(controller.getIsEdit() == true); + controller.cancel(); + System.assert(controller.getIsEdit() == false); + + System.assert(Page.ChangePassword.getUrl().equals(controller.changePassword().getUrl())); + + String randFax = Math.rint(Math.random() * 1000) + '5551234'; + controller.getUser().Fax = randFax; + controller.save(); + System.assert(controller.getIsEdit() == false); + + currentUser = [Select id, fax from User where id =: currentUser.Id]; + System.assert(currentUser.fax == randFax); + } else { + User existingPortalUser = existingPortalUsers[0]; + String randFax = Math.rint(Math.random() * 1000) + '5551234'; + + System.runAs(existingPortalUser) { + MyProfilePageController controller = new MyProfilePageController(); + System.assertEquals(existingPortalUser.Id, controller.getUser().Id, 'Did not successfully load the current user'); + System.assert(controller.getIsEdit() == false, 'isEdit should default to false'); + controller.edit(); + System.assert(controller.getIsEdit() == true); + + controller.cancel(); + System.assert(controller.getIsEdit() == false); + + controller.getUser().Fax = randFax; + controller.save(); + System.assert(controller.getIsEdit() == false); + } + + // verify that the user was updated + existingPortalUser = [Select id, fax from User where id =: existingPortalUser.Id]; + System.assert(existingPortalUser.fax == randFax); + } + } +} \ No newline at end of file diff --git a/examples/main/default/classes/MyProfilePageControllerTest.cls-meta.xml b/examples/main/default/classes/MyProfilePageControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/MyProfilePageControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/SiteLoginController.cls b/examples/main/default/classes/SiteLoginController.cls new file mode 100644 index 0000000..fb2f4c7 --- /dev/null +++ b/examples/main/default/classes/SiteLoginController.cls @@ -0,0 +1,14 @@ +/** + * An apex page controller that exposes the site login functionality + */ +global with sharing class SiteLoginController { + global String username {get; set;} + global String password {get; set;} + + global PageReference login() { + String startUrl = System.currentPageReference().getParameters().get('startURL'); + return Site.login(username, password, startUrl); + } + + global SiteLoginController () {} +} \ No newline at end of file diff --git a/examples/main/default/classes/SiteLoginController.cls-meta.xml b/examples/main/default/classes/SiteLoginController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/SiteLoginController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/SiteLoginControllerTest.cls b/examples/main/default/classes/SiteLoginControllerTest.cls new file mode 100644 index 0000000..88ec4a9 --- /dev/null +++ b/examples/main/default/classes/SiteLoginControllerTest.cls @@ -0,0 +1,13 @@ +/** + * An apex page controller that exposes the site login functionality + */ +@IsTest global with sharing class SiteLoginControllerTest { + @IsTest(SeeAllData=true) global static void testSiteLoginController () { + // Instantiate a new controller with all parameters in the page + SiteLoginController controller = new SiteLoginController (); + controller.username = 'test@salesforce.com'; + controller.password = '123456'; + + System.assertEquals(controller.login(),null); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/SiteLoginControllerTest.cls-meta.xml b/examples/main/default/classes/SiteLoginControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/SiteLoginControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/SiteRegisterController.cls b/examples/main/default/classes/SiteRegisterController.cls new file mode 100644 index 0000000..9332bc4 --- /dev/null +++ b/examples/main/default/classes/SiteRegisterController.cls @@ -0,0 +1,50 @@ +/** + * An apex class that creates a portal user + */ +public with sharing class SiteRegisterController { + // PORTAL_ACCOUNT_ID is the account on which the contact will be created on and then enabled as a portal user. + // you need to add the account owner into the role hierarchy before this will work - please see Customer Portal Setup help for more information. + private static Id PORTAL_ACCOUNT_ID = '001x000xxx35tPN'; + + public SiteRegisterController () { + } + + public String username {get; set;} + public String email {get; set;} + public String password {get; set {password = value == null ? value : value.trim(); } } + public String confirmPassword {get; set { confirmPassword = value == null ? value : value.trim(); } } + public String communityNickname {get; set { communityNickname = value == null ? value : value.trim(); } } + + private boolean isValidPassword() { + return password == confirmPassword; + } + + public PageReference registerUser() { + // it's okay if password is null - we'll send the user a random password in that case + if (!isValidPassword()) { + ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, Label.site.passwords_dont_match); + ApexPages.addMessage(msg); + return null; + } + User u = new User(); + u.Username = username; + u.Email = email; + u.CommunityNickname = communityNickname; + + String accountId = PORTAL_ACCOUNT_ID; + + // lastName is a required field on user, but if it isn't specified, we'll default it to the username + String userId = Site.createPortalUser(u, accountId, password); + if (userId != null) { + if (password != null && password.length() > 1) { + return Site.login(username, password, null); + } + else { + PageReference page = System.Page.SiteRegisterConfirm; + page.setRedirect(true); + return page; + } + } + return null; + } +} \ No newline at end of file diff --git a/examples/main/default/classes/SiteRegisterController.cls-meta.xml b/examples/main/default/classes/SiteRegisterController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/SiteRegisterController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/classes/SiteRegisterControllerTest.cls b/examples/main/default/classes/SiteRegisterControllerTest.cls new file mode 100644 index 0000000..79f27fa --- /dev/null +++ b/examples/main/default/classes/SiteRegisterControllerTest.cls @@ -0,0 +1,17 @@ +/** + * Class containing tests for SiteRegisterController + */ +@IsTest public with sharing class SiteRegisterControllerTest { + @IsTest(SeeAllData=true) static void testRegistration() { + SiteRegisterController controller = new SiteRegisterController(); + controller.username = 'test@force.com'; + controller.email = 'test@force.com'; + controller.communityNickname = 'test'; + // registerUser will always return null when the page isn't accessed as a guest user + System.assert(controller.registerUser() == null); + + controller.password = 'abcd1234'; + controller.confirmPassword = 'abcd123'; + System.assert(controller.registerUser() == null); + } +} \ No newline at end of file diff --git a/examples/main/default/classes/SiteRegisterControllerTest.cls-meta.xml b/examples/main/default/classes/SiteRegisterControllerTest.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/main/default/classes/SiteRegisterControllerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/main/default/components/SiteFooter.component b/examples/main/default/components/SiteFooter.component new file mode 100644 index 0000000..2023a51 --- /dev/null +++ b/examples/main/default/components/SiteFooter.component @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/examples/main/default/components/SiteFooter.component-meta.xml b/examples/main/default/components/SiteFooter.component-meta.xml new file mode 100644 index 0000000..b4861b0 --- /dev/null +++ b/examples/main/default/components/SiteFooter.component-meta.xml @@ -0,0 +1,6 @@ + + + 60.0 + Default Lightning Platform site footer component + + diff --git a/examples/main/default/components/SiteHeader.component b/examples/main/default/components/SiteHeader.component new file mode 100644 index 0000000..3ebfbe7 --- /dev/null +++ b/examples/main/default/components/SiteHeader.component @@ -0,0 +1,15 @@ + + + + + + {!$Label.site.login_button} + + {!$Label.site.forgot_your_password_q} + + {!$Label.site.new_user_q} + + {!$Label.site.logout} + + + \ No newline at end of file diff --git a/examples/main/default/components/SiteHeader.component-meta.xml b/examples/main/default/components/SiteHeader.component-meta.xml new file mode 100644 index 0000000..6a95c88 --- /dev/null +++ b/examples/main/default/components/SiteHeader.component-meta.xml @@ -0,0 +1,6 @@ + + + 60.0 + Default Lightning Platform site header component + + diff --git a/examples/main/default/components/SiteLogin.component b/examples/main/default/components/SiteLogin.component new file mode 100644 index 0000000..ec891e0 --- /dev/null +++ b/examples/main/default/components/SiteLogin.component @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + {!$Label.site.forgot_your_password_q} + + {!$Label.site.new_user_q} + + + + + \ No newline at end of file diff --git a/examples/main/default/components/SiteLogin.component-meta.xml b/examples/main/default/components/SiteLogin.component-meta.xml new file mode 100644 index 0000000..1ecdc38 --- /dev/null +++ b/examples/main/default/components/SiteLogin.component-meta.xml @@ -0,0 +1,6 @@ + + + 60.0 + Default Salesforce Sites Login component + + diff --git a/examples/main/default/components/SitePoweredBy.component b/examples/main/default/components/SitePoweredBy.component new file mode 100644 index 0000000..52b3779 --- /dev/null +++ b/examples/main/default/components/SitePoweredBy.component @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/examples/main/default/components/SitePoweredBy.component-meta.xml b/examples/main/default/components/SitePoweredBy.component-meta.xml new file mode 100644 index 0000000..0fcf410 --- /dev/null +++ b/examples/main/default/components/SitePoweredBy.component-meta.xml @@ -0,0 +1,6 @@ + + + 60.0 + Default Lightning Platform site powered by component + + diff --git a/examples/main/default/cspTrustedSites/Tailwind.cspTrustedSite-meta.xml b/examples/main/default/cspTrustedSites/Tailwind.cspTrustedSite-meta.xml new file mode 100644 index 0000000..bb1f9f6 --- /dev/null +++ b/examples/main/default/cspTrustedSites/Tailwind.cspTrustedSite-meta.xml @@ -0,0 +1,14 @@ + + + false + false + All + https://tailwindui.com + true + true + true + true + true + true + true + diff --git a/examples/main/default/digitalExperienceConfigs/examples1.digitalExperienceConfig-meta.xml b/examples/main/default/digitalExperienceConfigs/examples1.digitalExperienceConfig-meta.xml new file mode 100644 index 0000000..030c85e --- /dev/null +++ b/examples/main/default/digitalExperienceConfigs/examples1.digitalExperienceConfig-meta.xml @@ -0,0 +1,8 @@ + + + + + + + site/examples1 + diff --git a/examples/main/default/digitalExperiences/site/examples1/examples1.digitalExperience-meta.xml b/examples/main/default/digitalExperiences/site/examples1/examples1.digitalExperience-meta.xml new file mode 100644 index 0000000..eed22ae --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/examples1.digitalExperience-meta.xml @@ -0,0 +1,4 @@ + + + + diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/_meta.json new file mode 100644 index 0000000..676a917 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "mainAppPage", + "type" : "sfdc_cms__appPage", + "path" : "" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/content.json new file mode 100644 index 0000000..346fe17 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__appPage", + "title" : "main", + "contentBody" : { + "currentThemeId" : "Build_Your_Own_LWR", + "headMarkup" : "\n\nWelcome to LWC Communities!\n\n\n\r\n\n\r\n\n\n\n\n\n\n\n\n\n\n", + "isLockerServiceEnabled" : true, + "isRelaxedCSPLevel" : false, + "templateName" : "talon-template-byo" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/_meta.json new file mode 100644 index 0000000..ab11b60 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Build_Your_Own_LWR", + "type" : "sfdc_cms__brandingSet", + "path" : "brandingSets" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/content.json new file mode 100644 index 0000000..e626fec --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/content.json @@ -0,0 +1,183 @@ +{ + "type" : "sfdc_cms__brandingSet", + "title" : "Build Your Own (LWR)", + "contentBody" : { + "brandingSetType" : "APP", + "definitionName" : "talon-template-byo:branding", + "values" : { + "BackgroundColor" : "#ffffff", + "BaseFontSize" : "1rem", + "BodyFont" : "Salesforce Sans", + "BodyFontSize" : "1rem", + "BodyFontStyle" : "normal", + "BodyFontWeight" : "400", + "BodyLetterSpacing" : "0em", + "BodyLineHeight" : "1.5", + "BodySmallFont" : "Salesforce Sans", + "BodySmallFontSize" : "0.75rem", + "BodySmallFontStyle" : "normal", + "BodySmallFontWeight" : "400", + "BodySmallLetterSpacing" : "0em", + "BodySmallLineHeight" : "1.25", + "BodySmallTextColor" : "var(--dxp-g-root-contrast)", + "BodySmallTextDecoration" : "none", + "BodySmallTextTransform" : "none", + "BodyTextColor" : "var(--dxp-g-root-contrast)", + "BodyTextDecoration" : "none", + "BodyTextTransform" : "none", + "ButtonActiveColor" : "var(--dxp-s-button-color-1)", + "ButtonBorderRadius" : "4px", + "ButtonColor" : "var(--dxp-g-brand)", + "ButtonFocusColor" : "var(--dxp-s-button-color-1)", + "ButtonFont" : "Salesforce Sans", + "ButtonFontSize" : "1rem", + "ButtonFontStyle" : "normal", + "ButtonFontWeight" : "400", + "ButtonHoverColor" : "var(--dxp-s-button-color-1)", + "ButtonLargeBorderRadius" : "4px", + "ButtonLargeFontSize" : "1.25rem", + "ButtonLargePadding" : "1.25rem", + "ButtonLetterSpacing" : "0em", + "ButtonLineHeight" : "2", + "ButtonPadding" : "1rem", + "ButtonSmallBorderRadius" : "4px", + "ButtonSmallFontSize" : "0.75rem", + "ButtonSmallPadding" : "0.75rem", + "ButtonTextTransform" : "none", + "ColumnSpacerSizeDesktop" : "1rem", + "ColumnSpacerSizeMobile" : "0.75rem", + "ComponentSpacerSizeDesktop" : "1.5rem", + "ComponentSpacerSizeMobile" : "1.5rem", + "DropdownBackgroundColor" : "var(--dxp-g-root)", + "DropdownBackgroundHoverColor" : "var(--dxp-g-neutral)", + "DropdownBorderColor" : "var(--dxp-g-neutral)", + "DropdownTextColor" : "var(--dxp-g-root-contrast)", + "DropdownTextHoverColor" : "var(--dxp-g-neutral-contrast)", + "FormElementBackgroundColor" : "var(--dxp-g-root)", + "FormElementBorderColor" : "var(--dxp-g-neutral-3)", + "FormElementBorderRadius" : "4px", + "FormElementBorderWidth" : "1px", + "FormElementLabelColor" : "var(--dxp-g-root-contrast)", + "FormElementTextColor" : "var(--dxp-g-root-contrast)", + "HeadingExtraLargeColor" : "var(--dxp-g-root-contrast)", + "HeadingExtraLargeFont" : "Salesforce Sans", + "HeadingExtraLargeFontSize" : "2.5rem", + "HeadingExtraLargeFontStyle" : "normal", + "HeadingExtraLargeFontWeight" : "300", + "HeadingExtraLargeLetterSpacing" : "0em", + "HeadingExtraLargeLineHeight" : "1.25", + "HeadingExtraLargeTextDecoration" : "none", + "HeadingExtraLargeTextTransform" : "none", + "HeadingLargeColor" : "var(--dxp-g-root-contrast)", + "HeadingLargeFont" : "Salesforce Sans", + "HeadingLargeFontSize" : "1.75rem", + "HeadingLargeFontStyle" : "normal", + "HeadingLargeFontWeight" : "300", + "HeadingLargeLetterSpacing" : "0em", + "HeadingLargeLineHeight" : "1.25", + "HeadingLargeTextDecoration" : "none", + "HeadingLargeTextTransform" : "none", + "HeadingMediumColor" : "var(--dxp-g-root-contrast)", + "HeadingMediumFont" : "Salesforce Sans", + "HeadingMediumFontSize" : "1.25rem", + "HeadingMediumFontStyle" : "normal", + "HeadingMediumFontWeight" : "300", + "HeadingMediumLetterSpacing" : "0em", + "HeadingMediumLineHeight" : "1.25", + "HeadingMediumTextDecoration" : "none", + "HeadingMediumTextTransform" : "none", + "HeadingSmallColor" : "var(--dxp-g-root-contrast)", + "HeadingSmallFont" : "Salesforce Sans", + "HeadingSmallFontSize" : "1.125rem", + "HeadingSmallFontStyle" : "normal", + "HeadingSmallFontWeight" : "300", + "HeadingSmallLetterSpacing" : "0em", + "HeadingSmallLineHeight" : "1.25", + "HeadingSmallTextDecoration" : "none", + "HeadingSmallTextTransform" : "none", + "HorizontalRowPaddingDesktop" : "1rem", + "HorizontalRowPaddingMobile" : "0.75rem", + "LinkColor" : "var(--dxp-g-brand)", + "LinkHoverColor" : "var(--dxp-s-link-text-color-1)", + "LinkTextDecoration" : "none", + "LinkTextDecorationFocus" : "underline", + "LinkTextDecorationHover" : "underline", + "MaxContentWidthDesktop" : "1800px", + "MaxContentWidthMobile" : "none", + "MobileBaseFontSize" : "1rem", + "PrimaryAccentColor" : "#005fb2", + "PrimaryAccentForegroundColor" : "#ffffff", + "SiteLogo" : "", + "TextColor" : "#1a1b1e", + "VerticalRowPaddingDesktop" : "1rem", + "VerticalRowPaddingMobile" : "0.75rem", + "_BackgroundColor1" : "#ebebeb", + "_BackgroundColor2" : "#c2c2c2", + "_BackgroundColor3" : "#858585", + "_ButtonActiveColorContrast" : "var(--dxp-g-brand-contrast-1)", + "_ButtonColor1" : "var(--dxp-g-brand-1)", + "_ButtonColorContrast" : "var(--dxp-g-brand-contrast)", + "_ButtonFocusColorContrast" : "var(--dxp-g-brand-contrast-1)", + "_ButtonHoverColorContrast" : "var(--dxp-g-brand-contrast-1)", + "_DestructiveColor" : "#c23934", + "_DestructiveColor1" : "#a2302b", + "_DestructiveColor2" : "#611d1a", + "_DestructiveColor3" : "#010000", + "_DestructiveForegroundColor" : "#ffffff", + "_DestructiveForegroundColor1" : "#ffffff", + "_DestructiveForegroundColor2" : "#ffffff", + "_DestructiveForegroundColor3" : "#ffffff", + "_InfoColor" : "#16325c", + "_InfoColor1" : "#0e203b", + "_InfoColor2" : "#000000", + "_InfoColor3" : "#000000", + "_InfoForegroundColor" : "#ffffff", + "_InfoForegroundColor1" : "#ffffff", + "_InfoForegroundColor2" : "#ffffff", + "_InfoForegroundColor3" : "#ffffff", + "_LinkColor1" : "var(--dxp-g-brand-1)", + "_NeutralColor" : "#ecebea", + "_NeutralColor1" : "#d9d7d5", + "_NeutralColor2" : "#b2aeaa", + "_NeutralColor3" : "#76716b", + "_NeutralForegroundColor" : "#000000", + "_NeutralForegroundColor1" : "#000000", + "_NeutralForegroundColor2" : "#000000", + "_NeutralForegroundColor3" : "#ffffff", + "_OfflineColor" : "#444444", + "_OfflineColor1" : "#303030", + "_OfflineColor2" : "#070707", + "_OfflineColor3" : "#000000", + "_OfflineForegroundColor" : "#ffffff", + "_OfflineForegroundColor1" : "#ffffff", + "_OfflineForegroundColor2" : "#ffffff", + "_OfflineForegroundColor3" : "#ffffff", + "_PrimaryAccentColor1" : "#004989", + "_PrimaryAccentColor2" : "#001e38", + "_PrimaryAccentColor3" : "#000000", + "_PrimaryAccentForegroundColor1" : "#ffffff", + "_PrimaryAccentForegroundColor2" : "#ffffff", + "_PrimaryAccentForegroundColor3" : "#ffffff", + "_SiteLogoUrl" : "", + "_SuccessColor" : "#4bca81", + "_SuccessColor1" : "#36b66c", + "_SuccessColor2" : "#237747", + "_SuccessColor3" : "#07190f", + "_SuccessForegroundColor" : "#000000", + "_SuccessForegroundColor1" : "#000000", + "_SuccessForegroundColor2" : "#ffffff", + "_SuccessForegroundColor3" : "#ffffff", + "_TextColor1" : "#000000", + "_TextColor2" : "#000000", + "_TextColor3" : "#000000", + "_WarningColor" : "#ffb75d", + "_WarningColor1" : "#ffa534", + "_WarningColor2" : "#e27d00", + "_WarningColor3" : "#673900", + "_WarningForegroundColor" : "#000000", + "_WarningForegroundColor1" : "#000000", + "_WarningForegroundColor2" : "#000000", + "_WarningForegroundColor3" : "#ffffff" + } + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/_meta.json new file mode 100644 index 0000000..d0381dc --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "languages", + "type" : "sfdc_cms__languageSettings", + "path" : "_settings" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/content.json new file mode 100644 index 0000000..97abb57 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/content.json @@ -0,0 +1,13 @@ +{ + "type" : "sfdc_cms__languageSettings", + "title" : "LanguageContent", + "contentBody" : { + "languages" : [ { + "locale" : "en_US", + "label" : "English (US)", + "isActive" : true, + "isAuthoringOnly" : false + } ], + "defaultLocale" : "en_US" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/_meta.json new file mode 100644 index 0000000..0fa36bf --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "mobilePublisherConfig", + "type" : "sfdc_cms__mobilePublisherConfig", + "path" : "_settings" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/content.json new file mode 100644 index 0000000..a8b205d --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/content.json @@ -0,0 +1,46 @@ +{ + "type" : "sfdc_cms__mobilePublisherConfig", + "title" : "Mobile Publisher Content", + "contentBody" : { + "nativeTabMenu" : { + "branding" : { + "iconTintColor" : "#0B5CAB", + "iconTintColorUnselected" : "#C9C9C9", + "barTintColor" : "#FFFFFF" + }, + "menuItems" : [ { + "name" : "Home", + "targetUrl" : "/", + "iconDetails" : { + "source" : { + "type" : "salesforceAsset", + "salesforceAssetName" : "slds-icon:home" + } + } + } ] + }, + "nativeMobileNavConfig" : { + "global" : { + "showHamburgerMenu" : true + }, + "ios" : { + "showBackButton" : true, + "showHamburgerMenuWithBackButton" : false + } + }, + "mobilePublisherAppUpdateConfig" : { + "enableAppUpdate" : false, + "forceAppUpdate" : false, + "minVersion" : { + "ios" : { + "url" : "https://apps.apple.com/us", + "version" : "10.0" + }, + "android" : { + "url" : "https://play.google.com/store", + "version" : "10.1" + } + } + } + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/_meta.json new file mode 100644 index 0000000..f3856da --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Check_Password", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/content.json new file mode 100644 index 0000000..7bac5cb --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Check Password", + "contentBody" : { + "activeViewId" : "checkPasswordResetEmail", + "configurationTags" : [ ], + "pageAccess" : "UseParent", + "routeType" : "check-password", + "urlPrefix" : "CheckPasswordResetEmail" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/_meta.json new file mode 100644 index 0000000..0df70e2 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Checkout__c", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/content.json new file mode 100644 index 0000000..38bb450 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Checkout", + "contentBody" : { + "activeViewId" : "Checkout", + "configurationTags" : [ ], + "pageAccess" : "UseParent", + "routeType" : "custom-checkout", + "urlPrefix" : "checkout" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/_meta.json new file mode 100644 index 0000000..5ce69b0 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Error", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/content.json new file mode 100644 index 0000000..879079a --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Error", + "contentBody" : { + "activeViewId" : "error", + "configurationTags" : [ ], + "pageAccess" : "UseParent", + "routeType" : "error", + "urlPrefix" : "error" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/_meta.json new file mode 100644 index 0000000..e421ecb --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Forgot_Password", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/content.json new file mode 100644 index 0000000..66d9662 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Forgot Password", + "contentBody" : { + "activeViewId" : "forgotPassword", + "configurationTags" : [ ], + "pageAccess" : "UseParent", + "routeType" : "forgot-password", + "urlPrefix" : "ForgotPassword" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/_meta.json new file mode 100644 index 0000000..cd9f8a4 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Home", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/content.json new file mode 100644 index 0000000..bc24463 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Home", + "contentBody" : { + "activeViewId" : "home", + "configurationTags" : [ ], + "pageAccess" : "UseParent", + "routeType" : "home", + "urlPrefix" : "" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/_meta.json new file mode 100644 index 0000000..ad05a8c --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Login", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/content.json new file mode 100644 index 0000000..a019936 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Login", + "contentBody" : { + "activeViewId" : "login", + "configurationTags" : [ ], + "pageAccess" : "UseParent", + "routeType" : "login-main", + "urlPrefix" : "login" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/_meta.json new file mode 100644 index 0000000..6101581 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "News_Detail__c", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/content.json new file mode 100644 index 0000000..97d5d6d --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "News Detail", + "contentBody" : { + "activeViewId" : "newsDetail", + "configurationTags" : [ ], + "pageAccess" : "UseParent", + "routeType" : "managed-content-sfdc_cms__news", + "urlPrefix" : "news" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/_meta.json new file mode 100644 index 0000000..bce40ba --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Register", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/content.json new file mode 100644 index 0000000..81cd435 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Register", + "contentBody" : { + "activeViewId" : "register", + "configurationTags" : [ ], + "pageAccess" : "UseParent", + "routeType" : "self-register", + "urlPrefix" : "SelfRegister" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/_meta.json new file mode 100644 index 0000000..6ec0efb --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Service_Not_Available", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/content.json new file mode 100644 index 0000000..ddedb11 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Service Not Available", + "contentBody" : { + "activeViewId" : "serviceNotAvailable", + "configurationTags" : [ "allow-in-static-site" ], + "pageAccess" : "UseParent", + "routeType" : "service-not-available", + "urlPrefix" : "service-not-available" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/_meta.json new file mode 100644 index 0000000..2d88e32 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Too_Many_Requests", + "type" : "sfdc_cms__route", + "path" : "routes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/content.json new file mode 100644 index 0000000..42413d9 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/content.json @@ -0,0 +1,11 @@ +{ + "type" : "sfdc_cms__route", + "title" : "Too Many Requests", + "contentBody" : { + "activeViewId" : "tooManyRequests", + "configurationTags" : [ "too-many-requests", "allow-in-static-site" ], + "pageAccess" : "UseParent", + "routeType" : "too-many-requests", + "urlPrefix" : "too-many-requests" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/_meta.json new file mode 100644 index 0000000..cb37c96 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "examples1", + "type" : "sfdc_cms__site", + "path" : "" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/content.json new file mode 100644 index 0000000..b286a50 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/content.json @@ -0,0 +1,7 @@ +{ + "type" : "sfdc_cms__site", + "title" : "examples", + "contentBody" : { + "authenticationType" : "AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/_meta.json new file mode 100644 index 0000000..9fa3d0d --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "print_css", + "type" : "sfdc_cms__styles", + "path" : "" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/content.json new file mode 100644 index 0000000..3751772 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/content.json @@ -0,0 +1,15 @@ +{ + "type" : "sfdc_cms__styles", + "title" : "print.css", + "contentBody" : { + "sfdc_cms:media" : { + "source" : { + "type" : "file", + "ref" : "0sN7z0000004OF7", + "size" : 1013, + "mimeType" : "text/css" + }, + "url" : "/cms/media/MCNHKZKVNYSRGLDJDYQCX7PXJEHA?version=1.1" + } + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/print.css b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/print.css new file mode 100644 index 0000000..37937fe --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/print.css @@ -0,0 +1,36 @@ +@media print { + /* Hide href for all links */ + /* a[href]:after { + content: none !important; + } */ + + /* Example: hides header and footer for Customer Service template */ + /* #header, #footer { + display: none !important + } */ + + /* Example: hides header for Customer Account and Partner Central template */ + /* .cHeaderWrapper, .cFooterPanel { + display: none !important; + } */ + + /* Example: hides header and footer for Help Center template */ + /* .header, .footer { + display: none !important; + } */ + + /* Example: hides header and footer for Build Your Own (Aura) template */ + /* .comm-content-header, .comm-content-footer { + display: none !important; + } */ + + /* Example: hides header and footer for LWR templates */ + /* header, footer { + display: none !important; + } */ + + /* Example: hides the aura Navigation Menu component */ + /* .forceCommunityGlobalNavigation { + display: none !important; + } */ +} diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/_meta.json new file mode 100644 index 0000000..95f382d --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "styles_css", + "type" : "sfdc_cms__styles", + "path" : "" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/content.json new file mode 100644 index 0000000..115b776 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/content.json @@ -0,0 +1,15 @@ +{ + "type" : "sfdc_cms__styles", + "title" : "styles.css", + "contentBody" : { + "sfdc_cms:media" : { + "source" : { + "type" : "file", + "ref" : "0sN7z0000004OF6", + "size" : 125, + "mimeType" : "text/css" + }, + "url" : "/cms/media/MC6V5FDF6HDRG25KCS5L4Y7EOAKU?version=1.1" + } + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/styles.css b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/styles.css new file mode 100644 index 0000000..519a43f --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/styles.css @@ -0,0 +1,15 @@ +/* + * Your global styles here + */ +html, +body { + height: 100%; +} + +html { + background: white; +} + +body { + margin: 0; +} diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/_meta.json new file mode 100644 index 0000000..200d629 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Build_Your_Own_LWR", + "type" : "sfdc_cms__theme", + "path" : "themes" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/content.json new file mode 100644 index 0000000..5761516 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/content.json @@ -0,0 +1,15 @@ +{ + "type" : "sfdc_cms__theme", + "title" : "Build Your Own (LWR)", + "contentBody" : { + "activeBrandingSetId" : "Build_Your_Own_LWR", + "definitionName" : "byo", + "layouts" : [ { + "layoutId" : "snaThemeLayout", + "layoutType" : "ServiceNotAvailable" + }, { + "layoutId" : "scopedHeaderAndFooter", + "layoutType" : "Inner" + } ] + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/_meta.json new file mode 100644 index 0000000..90769ae --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "scopedHeaderAndFooter", + "type" : "sfdc_cms__themeLayout", + "path" : "themeLayouts" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/content.json new file mode 100644 index 0000000..ad947ef --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/content.json @@ -0,0 +1,61 @@ +{ + "type" : "sfdc_cms__themeLayout", + "title" : "Scoped Header and Footer", + "contentBody" : { + "component" : { + "attributes" : { }, + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"65c6e3b6-533c-4eb2-b535-24dfdcd989f5\",\"columns\":[{\"UUID\":\"d2eddd69-b9a2-4fd0-8877-2075ab61251c\",\"columnName\":\"Column 1\",\"columnKey\":\"headerSection\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "id" : "d2eddd69-b9a2-4fd0-8877-2075ab61251c", + "name" : "headerSection", + "title" : "Theme Header", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "65c6e3b6-533c-4eb2-b535-24dfdcd989f5", + "type" : "component" + } ], + "id" : "148290c6-fa59-4c00-9db5-e1243d78c25a", + "name" : "header", + "title" : "Theme Header", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"fa19eed3-c2f9-479e-8e5c-dfe81ac8b8f6\",\"columns\":[{\"UUID\":\"0a06af77-6814-47ec-86ed-7772ca96098b\",\"columnName\":\"Column 1\",\"columnKey\":\"footerSection\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "id" : "0a06af77-6814-47ec-86ed-7772ca96098b", + "name" : "footerSection", + "title" : "Theme Footer", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "fa19eed3-c2f9-479e-8e5c-dfe81ac8b8f6", + "type" : "component" + } ], + "id" : "a7a4d1a4-7ea6-4776-b1f4-123a66c838c5", + "name" : "footer", + "title" : "Theme Footer", + "type" : "region" + } ], + "definition" : "community_byo:scopedHeaderAndFooter", + "id" : "84e48005-22ec-4b53-b058-4d62e0419d40", + "type" : "component" + } + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/_meta.json new file mode 100644 index 0000000..ac7ee60 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "snaThemeLayout", + "type" : "sfdc_cms__themeLayout", + "path" : "themeLayouts" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/content.json new file mode 100644 index 0000000..b6bc9ba --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/content.json @@ -0,0 +1,23 @@ +{ + "type" : "sfdc_cms__themeLayout", + "title" : "Service Not Available Theme Layout", + "contentBody" : { + "component" : { + "attributes" : { }, + "children" : [ { + "id" : "95b3aae8-a63f-4f2a-8366-45d43b492d80", + "name" : "header", + "title" : "Theme Header", + "type" : "region" + }, { + "id" : "d6cbb1e9-b32e-4de4-8b36-a5cff8c32809", + "name" : "footer", + "title" : "Theme Footer", + "type" : "region" + } ], + "definition" : "community_layout:simpleThemeLayout", + "id" : "48bc63c6-e678-4938-9cea-c3edd83b3112", + "type" : "component" + } + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/_meta.json new file mode 100644 index 0000000..aadb55b --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "Checkout", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/content.json new file mode 100644 index 0000000..6a955ac --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/content.json @@ -0,0 +1,71 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Checkout", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"0130a194-0b9d-4194-8e2c-47f627cafca8\",\"columns\":[{\"UUID\":\"2d6af6c8-95f7-4297-9ee9-6d33f0258647\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":[]}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { }, + "definition" : "c:shoppingCartDetails", + "id" : "bf9c3261-d87f-47e6-b000-8b2597a51496", + "type" : "component" + }, { + "attributes" : { }, + "definition" : "c:orderSummary", + "id" : "4c59e794-6e57-4802-81ef-d11f0c503241", + "type" : "component" + }, { + "attributes" : { }, + "definition" : "c:checkoutButton", + "id" : "3ddd65b5-d783-4ecb-9921-91709428a735", + "type" : "component" + } ], + "id" : "2d6af6c8-95f7-4297-9ee9-6d33f0258647", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "0130a194-0b9d-4194-8e2c-47f627cafca8", + "type" : "component" + } ], + "id" : "20d6af3d-694c-465d-8498-c68b961a80d7", + "name" : "content", + "title" : "Content", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "customHeadTags" : "", + "description" : "", + "pageTitle" : "Checkout", + "recordId" : "{!recordId}" + }, + "definition" : "community_builder:seoAssistant", + "id" : "1f185f02-ddd3-4137-8f99-4b3508ad79c5", + "type" : "component" + } ], + "id" : "87d27a2a-dc32-4142-b244-e5cd51ec16b8", + "name" : "sfdcHiddenRegion", + "title" : "sfdcHiddenRegion", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "c791f994-38f9-41dc-a433-2149ea0969aa", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "Inner", + "viewType" : "custom-checkout" + } +} diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/_meta.json new file mode 100644 index 0000000..9135294 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "checkPasswordResetEmail", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/content.json new file mode 100644 index 0000000..06a36d4 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/content.json @@ -0,0 +1,74 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Check Password", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"86788f8c-779f-42c3-ad27-8124aa3b2a71\",\"columns\":[{\"UUID\":\"645bff85-5f13-4a97-b1d7-ffc10e5e10fb\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { + "alignment" : "left", + "imageInfo" : "", + "logoWidth" : 250 + }, + "definition" : "dxp_content_layout:siteLogo", + "id" : "05c35e7f-e530-41e5-8b38-554c95323454", + "type" : "component" + }, { + "attributes" : { + "checkEmailMessage" : "Check the email account associated with your username for the link to reset your password. If you didn't get an email, check your Spam folder. Or contact your administrator.", + "returnButtonLabel" : "Back to login", + "titleLabel" : "Now check your email" + }, + "definition" : "community_login:checkEmail", + "id" : "9f2d6f92-8e2e-4929-bc5d-67ef7b125cb0", + "type" : "component" + } ], + "id" : "645bff85-5f13-4a97-b1d7-ffc10e5e10fb", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "86788f8c-779f-42c3-ad27-8124aa3b2a71", + "type" : "component" + } ], + "id" : "df649a2c-6f89-45a0-93d0-e6129a444004", + "name" : "content", + "title" : "Content", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "customHeadTags" : "", + "description" : "", + "pageTitle" : "Check Password", + "recordId" : "{!recordId}" + }, + "definition" : "community_builder:seoAssistant", + "id" : "39c2dc11-b5cd-4c25-855f-0739c06436fa", + "type" : "component" + } ], + "id" : "140f175d-8005-47ce-aacd-38f00b6cfec1", + "name" : "sfdcHiddenRegion", + "title" : "sfdcHiddenRegion", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "1f0fa9e1-6e98-4c32-abca-6116a231ecca", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "Inner", + "viewType" : "check-password" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/_meta.json new file mode 100644 index 0000000..f9287cb --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "error", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/content.json new file mode 100644 index 0000000..8c20c8f --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/content.json @@ -0,0 +1,63 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Error", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"7c1d48be-3535-416c-b05a-b1a3e4c4facd\",\"columns\":[{\"UUID\":\"d860137b-2783-420d-88ea-8b7144e752b2\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { + "richTextValue" : "

Invalid Page

" + }, + "definition" : "community_builder:richTextEditor", + "id" : "348080be-692f-4ea7-b2dc-92edb50248c3", + "type" : "component" + } ], + "id" : "d860137b-2783-420d-88ea-8b7144e752b2", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "7c1d48be-3535-416c-b05a-b1a3e4c4facd", + "type" : "component" + } ], + "id" : "515ec8a6-2dfe-461f-a2e3-1c615bc3ab6a", + "name" : "content", + "title" : "Content", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "customHeadTags" : "", + "description" : "", + "pageTitle" : "Error", + "recordId" : "{!recordId}" + }, + "definition" : "community_builder:seoAssistant", + "id" : "54e69473-45ee-4b4d-89e0-b0ccc0ebd6ad", + "type" : "component" + } ], + "id" : "2ad828de-2c3e-4092-af2d-c2fc7a450654", + "name" : "sfdcHiddenRegion", + "title" : "sfdcHiddenRegion", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "24b4958a-34f0-4b92-beb2-0256971dfef2", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "Inner", + "viewType" : "error" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/_meta.json new file mode 100644 index 0000000..dd6ab2c --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "forgotPassword", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/content.json new file mode 100644 index 0000000..414b5ab --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/content.json @@ -0,0 +1,77 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Forgot Password", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"126ea3c4-03c1-4520-8c52-941678989028\",\"columns\":[{\"UUID\":\"54219043-cc18-46c4-91a7-25d6d5bc4ec7\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { + "alignment" : "left", + "imageInfo" : "", + "logoWidth" : 250 + }, + "definition" : "dxp_content_layout:siteLogo", + "id" : "f7a4992d-2bfa-453f-9d35-b9148cee814b", + "type" : "component" + }, { + "attributes" : { + "cancelButtonLabel" : "Cancel", + "checkEmailUrl" : "./CheckPasswordResetEmail", + "instructionsLabel" : "To reset your password, enter your username. We'll send a reset-password link to the email address associated with your account.", + "submitButtonLabel" : "Reset", + "titleLabel" : "Forgot your password?", + "usernameLabel" : "Username" + }, + "definition" : "community_login:forgotPassword", + "id" : "871a0b8a-9b04-4bb1-b12b-edf66f2fe4c4", + "type" : "component" + } ], + "id" : "54219043-cc18-46c4-91a7-25d6d5bc4ec7", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "126ea3c4-03c1-4520-8c52-941678989028", + "type" : "component" + } ], + "id" : "36b80763-fb3b-42e6-8a5a-bd76273f133e", + "name" : "content", + "title" : "Content", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "customHeadTags" : "", + "description" : "", + "pageTitle" : "Forgot Password", + "recordId" : "{!recordId}" + }, + "definition" : "community_builder:seoAssistant", + "id" : "418e023a-7db1-4606-9b60-8282d3444e7f", + "type" : "component" + } ], + "id" : "df768e12-6c2e-4602-8953-eb0fbaeee214", + "name" : "sfdcHiddenRegion", + "title" : "sfdcHiddenRegion", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "c9cd673c-2d7d-414a-987e-2b3f2435397d", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "Inner", + "viewType" : "forgot-password" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/_meta.json new file mode 100644 index 0000000..0ebd40d --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "home", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/content.json new file mode 100644 index 0000000..bde8046 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/content.json @@ -0,0 +1,83 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Home", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"45e97f2a-56ff-4067-a47d-20024a315ba1\",\"columns\":[{\"UUID\":\"142284b4-88b0-4d00-ad59-6d177998602a\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { + "alignment" : "center", + "size" : "standard", + "stretch" : "standard", + "text" : "Checkout Demo", + "type" : "tertiary", + "url" : "{\"linkInfo\":{\"pageReference\":{\"type\":\"comm__namedPage\",\"attributes\":{\"name\":\"Checkout__c\"}}}}" + }, + "definition" : "dxp_base:button", + "id" : "c4f0a4d8-6c9a-43dc-83fd-1ea9be17ef6a", + "type" : "component" + }, { + "attributes" : { + "backgroundColor" : "", + "isLinkable" : false, + "linkAssistiveText" : "", + "paddingHorizontal" : "none", + "paddingVertical" : "none", + "text" : "Demonstrates how to create a custom storage solution that implements Undo functionality.", + "textAlign" : "center", + "textDecoration" : "{}", + "textDisplayInfo" : "{}" + }, + "definition" : "dxp_base:textBlock", + "id" : "5073049b-5c6f-42a8-a358-3232df2d813a", + "type" : "component" + } ], + "id" : "142284b4-88b0-4d00-ad59-6d177998602a", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "45e97f2a-56ff-4067-a47d-20024a315ba1", + "type" : "component" + } ], + "id" : "2800c82b-3f6c-4862-a597-01804f32f2ee", + "name" : "content", + "title" : "Content", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "customHeadTags" : "", + "description" : "", + "pageTitle" : "Home", + "recordId" : "{!recordId}" + }, + "definition" : "community_builder:seoAssistant", + "id" : "9c309199-8027-456d-989e-488a15ea84dc", + "type" : "component" + } ], + "id" : "03aa3d9c-2baf-4bf4-b0fc-cff3c60e8da7", + "name" : "sfdcHiddenRegion", + "title" : "sfdcHiddenRegion", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "bfc761b0-179e-4a18-98df-38cacf6d459a", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "Inner", + "viewType" : "home" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/_meta.json new file mode 100644 index 0000000..f956f58 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "login", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/content.json new file mode 100644 index 0000000..9124a1a --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/content.json @@ -0,0 +1,86 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Login", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"e6716faf-b263-4b5b-89d6-01993c62379e\",\"columns\":[{\"UUID\":\"1054f36e-a1f0-47db-a2d3-54270bdc43fb\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { + "alignment" : "left", + "imageInfo" : "", + "logoWidth" : 250 + }, + "definition" : "dxp_content_layout:siteLogo", + "id" : "470ded86-d520-4d9e-8ce7-e7e24de27e36", + "type" : "component" + }, { + "attributes" : { + "forgotPasswordLabel" : "Forgot your password?", + "forgotPasswordUrl" : "/ForgotPassword", + "loginButtonLabel" : "Log In", + "passwordLabel" : "Password", + "selfRegisterLabel" : "Not a member?", + "selfRegisterUrl" : "/SelfRegister", + "startUrl" : "", + "usernameLabel" : "Username" + }, + "definition" : "community_login:loginForm", + "id" : "60b8495c-a33d-4249-9e72-a8bf650fb3df", + "type" : "component" + }, { + "attributes" : { + "employeeLoginLinkLabel" : "Are you an employee? Log in" + }, + "definition" : "community_login:employeeLoginLink", + "id" : "dca57b66-3f61-47bc-a4bd-f1690c74b3d7", + "type" : "component" + } ], + "id" : "1054f36e-a1f0-47db-a2d3-54270bdc43fb", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "e6716faf-b263-4b5b-89d6-01993c62379e", + "type" : "component" + } ], + "id" : "ecfcea9d-1eb2-479b-a269-6a4c24b60f12", + "name" : "content", + "title" : "Content", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "customHeadTags" : "", + "description" : "", + "pageTitle" : "Login", + "recordId" : "{!recordId}" + }, + "definition" : "community_builder:seoAssistant", + "id" : "5e943f0b-2ecd-460f-9f9a-8fc7acb07ae0", + "type" : "component" + } ], + "id" : "ef1272ef-2d53-4a4f-8f3b-4e59151dc087", + "name" : "sfdcHiddenRegion", + "title" : "sfdcHiddenRegion", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "a9320ee5-26d9-462e-a8b8-eec050afa249", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "Inner", + "viewType" : "login-main" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/_meta.json new file mode 100644 index 0000000..464e1cc --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "newsDetail", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/content.json new file mode 100644 index 0000000..245b4fc --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/content.json @@ -0,0 +1,55 @@ +{ + "type" : "sfdc_cms__view", + "title" : "News Detail", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"963ee36d-a489-41c5-b6ab-bc229e452aea\",\"columns\":[{\"UUID\":\"9b8d5e6b-df0c-49eb-afa7-6ce8578de929\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":[]}]}" + }, + "children" : [ { + "id" : "9b8d5e6b-df0c-49eb-afa7-6ce8578de929", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "963ee36d-a489-41c5-b6ab-bc229e452aea", + "type" : "component" + } ], + "id" : "f109d914-e97f-4885-905f-984004665a5a", + "name" : "content", + "title" : "Content", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "customHeadTags" : "", + "description" : "", + "pageTitle" : "{!Content.contentTypeLabel}.{!Content.title}", + "recordId" : "{!recordId}" + }, + "definition" : "community_builder:seoAssistant", + "id" : "da7f94ed-554c-4a9f-8f59-b11e63ed7092", + "type" : "component" + } ], + "id" : "c941a4e4-e6ed-4794-9061-1085b735cdba", + "name" : "sfdcHiddenRegion", + "title" : "sfdcHiddenRegion", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "0b0a3364-5403-4e78-8722-97792b5a9a50", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "Inner", + "viewType" : "managed-content-sfdc_cms__news" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/_meta.json new file mode 100644 index 0000000..c6e02c0 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "register", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/content.json new file mode 100644 index 0000000..96255e2 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/content.json @@ -0,0 +1,80 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Register", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"9e5e4548-6ec4-45d7-bc0d-530400408df0\",\"columns\":[{\"UUID\":\"45862d24-7b1d-4c3e-a1f8-411e0efccccb\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { + "alignment" : "left", + "imageInfo" : "", + "logoWidth" : 250 + }, + "definition" : "dxp_content_layout:siteLogo", + "id" : "ea67ff87-33d6-489f-8e77-4890e075aba4", + "type" : "component" + }, { + "attributes" : { + "cancelLinkLabel" : "Already have an account?", + "confirmPasswordLabel" : "Confirm Password", + "emailLabel" : "Email", + "firstnameLabel" : "First Name", + "includePasswordField" : true, + "lastnameLabel" : "Last Name", + "passwordLabel" : "Create Password", + "regConfirmUrl" : "./CheckPasswordResetEmail", + "submitButtonLabel" : "Sign Up" + }, + "definition" : "community_login:selfRegister", + "id" : "ab32d174-5fbf-4634-9af5-ae9e3747465b", + "type" : "component" + } ], + "id" : "45862d24-7b1d-4c3e-a1f8-411e0efccccb", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "9e5e4548-6ec4-45d7-bc0d-530400408df0", + "type" : "component" + } ], + "id" : "7a803148-0b06-48de-bcc8-d7b227e18bff", + "name" : "content", + "title" : "Content", + "type" : "region" + }, { + "children" : [ { + "attributes" : { + "customHeadTags" : "", + "description" : "", + "pageTitle" : "Register", + "recordId" : "{!recordId}" + }, + "definition" : "community_builder:seoAssistant", + "id" : "6668dff5-3720-433b-abc2-7afc5ce80e45", + "type" : "component" + } ], + "id" : "f9220ce3-4f4c-416b-acb7-30630a584abd", + "name" : "sfdcHiddenRegion", + "title" : "sfdcHiddenRegion", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "884d8cae-04a5-435f-ba3b-d022b7ed511a", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "Inner", + "viewType" : "self-register" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/_meta.json new file mode 100644 index 0000000..c1e4111 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "serviceNotAvailable", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/content.json new file mode 100644 index 0000000..4199b3c --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/content.json @@ -0,0 +1,47 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Service Not Available", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"35e27b49-6f9a-47df-a744-2633f3ae1270\",\"columns\":[{\"UUID\":\"23674f4c-9c38-45cd-9080-f349d4a7f050\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { + "richTextValue" : "
\n\t
\n
\n
\n\t

Looks like the site is temporarily unavailable

\n\t
\n\t

Please try again in a bit.

\n
" + }, + "definition" : "community_builder:htmlEditor", + "id" : "af94931c-e447-4120-b4f5-d652ca2b602b", + "type" : "component" + } ], + "id" : "23674f4c-9c38-45cd-9080-f349d4a7f050", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "35e27b49-6f9a-47df-a744-2633f3ae1270", + "type" : "component" + } ], + "id" : "07cb18c7-1eba-42b9-b274-e46a3a1413e7", + "name" : "content", + "title" : "Content", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "c3609148-f3c2-4158-bc1a-6c8381778e9a", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "ServiceNotAvailable", + "viewType" : "service-not-available" + } +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/_meta.json new file mode 100644 index 0000000..f23899d --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/_meta.json @@ -0,0 +1,5 @@ +{ + "apiName" : "tooManyRequests", + "type" : "sfdc_cms__view", + "path" : "views" +} \ No newline at end of file diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/content.json new file mode 100644 index 0000000..c9a1329 --- /dev/null +++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/content.json @@ -0,0 +1,52 @@ +{ + "type" : "sfdc_cms__view", + "title" : "Too Many Requests", + "contentBody" : { + "component" : { + "children" : [ { + "children" : [ { + "attributes" : { + "backgroundImageConfig" : "", + "backgroundImageOverlay" : "rgba(0,0,0,0)", + "componentSpacerSize" : "", + "maxContentWidth" : "", + "sectionColumnGutterWidth" : "", + "sectionConfig" : "{\"UUID\":\"a1fc7166-0b48-4928-9b6d-f6ad1f106725\",\"columns\":[{\"UUID\":\"dced948c-cf2b-449f-9384-f233da91d928\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}" + }, + "children" : [ { + "children" : [ { + "attributes" : { + "richTextValue" : "
\n\t
\n
\n
\n\t

Looks like the site is experiencing higher than usual demand…

\n\t

Don't go anywhere. We'll redirect you in a moment.

\n
" + }, + "definition" : "community_builder:htmlEditor", + "id" : "f54bb58b-1070-48fe-921e-21f46304d250", + "type" : "component" + }, { + "attributes" : { }, + "definition" : "experience_availability:autoRefresh", + "id" : "7e33bf4e-0a84-4cb9-8bf3-a03957a12774", + "type" : "component" + } ], + "id" : "dced948c-cf2b-449f-9384-f233da91d928", + "name" : "col1", + "title" : "Column 1", + "type" : "region" + } ], + "definition" : "community_layout:section", + "id" : "a1fc7166-0b48-4928-9b6d-f6ad1f106725", + "type" : "component" + } ], + "id" : "85d2dc00-5280-40a6-9ae7-b1014224ceda", + "name" : "content", + "title" : "Content", + "type" : "region" + } ], + "definition" : "community_layout:sldsFlexibleLayout", + "id" : "ef1fee5b-f825-43c0-81ea-8a71dcc99801", + "type" : "component" + }, + "dataProviders" : [ ], + "themeLayoutType" : "ServiceNotAvailable", + "viewType" : "too-many-requests" + } +} \ No newline at end of file diff --git a/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email b/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email new file mode 100644 index 0000000..4fe71e1 --- /dev/null +++ b/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email @@ -0,0 +1,8 @@ +Hi {!Receiving_User.FirstName}, + + Your password has been reset for {!Community_Name}. Go to: + + {!Community_Url} + + Thanks, + {!Organization.Name} \ No newline at end of file diff --git a/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email-meta.xml b/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email-meta.xml new file mode 100644 index 0000000..c822d36 --- /dev/null +++ b/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email-meta.xml @@ -0,0 +1,11 @@ + + + true + Notification of new password + UTF-8 + Communities: Changed Password Email + + Your new {!Community_Name} password + text + Aloha + diff --git a/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email b/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email new file mode 100644 index 0000000..b7fb22b --- /dev/null +++ b/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email @@ -0,0 +1,8 @@ +Hi {!Receiving_User.FirstName}, + + Your password has been reset for {!Community_Name}. Go to: + + {!Community_Url} + + Thanks, + {!Organization.Name} \ No newline at end of file diff --git a/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email-meta.xml b/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email-meta.xml new file mode 100644 index 0000000..16bd65b --- /dev/null +++ b/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email-meta.xml @@ -0,0 +1,11 @@ + + + true + Notification of new password when a user’s password is reset (because they forgot it) + UTF-8 + Communities: Forgot Password Email + + Your new {!Community_Name} password + text + Aloha + diff --git a/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email b/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email new file mode 100644 index 0000000..9f3df9a --- /dev/null +++ b/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email @@ -0,0 +1,8 @@ +Hi {!Receiving_User.FirstName}, + + Welcome to {!Community_Name}! To get started, go to {!Community_Url} + + Username: {!Receiving_User.Username} + + Thanks, + {!Organization.Name} \ No newline at end of file diff --git a/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email-meta.xml b/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email-meta.xml new file mode 100644 index 0000000..cf0e582 --- /dev/null +++ b/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email-meta.xml @@ -0,0 +1,11 @@ + + + true + Notification that user has been added to a community. + UTF-8 + Communities: New Member Welcome Email + + Welcome to {!Community_Name} + text + Aloha + diff --git a/examples/main/default/navigationMenus/SFDC_Default_Navigation_examples.navigationMenu-meta.xml b/examples/main/default/navigationMenus/SFDC_Default_Navigation_examples.navigationMenu-meta.xml new file mode 100644 index 0000000..086758a --- /dev/null +++ b/examples/main/default/navigationMenus/SFDC_Default_Navigation_examples.navigationMenu-meta.xml @@ -0,0 +1,13 @@ + + + examples + Network + + + + 1 + true + ShowMoreTopics + NavigationalTopic + + diff --git a/examples/main/default/networkBranding/cbexamples.networkBranding b/examples/main/default/networkBranding/cbexamples.networkBranding new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/examples/main/default/networkBranding/cbexamples.networkBranding @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/examples/main/default/networkBranding/cbexamples.networkBranding-meta.xml b/examples/main/default/networkBranding/cbexamples.networkBranding-meta.xml new file mode 100644 index 0000000..057c2bd --- /dev/null +++ b/examples/main/default/networkBranding/cbexamples.networkBranding-meta.xml @@ -0,0 +1,13 @@ + + + examples + #1797C0 + #FFFFFF + #B1BAC1 + #222222 + #51606E + #DDE4E9 + #222222 + #51606E + #FFFFFF + diff --git a/examples/main/default/networks/examples.network-meta.xml b/examples/main/default/networks/examples.network-meta.xml new file mode 100644 index 0000000..f567f34 --- /dev/null +++ b/examples/main/default/networks/examples.network-meta.xml @@ -0,0 +1,57 @@ + + + false + false + unfiled$public/CommunityChangePasswordEmailTemplate + + true + cesar.parra@gmail.com + examples + true + false + true + false + true + false + true + false + false + false + false + true + false + true + false + false + false + true + true + true + false + false + unfiled$public/CommunityForgotPasswordEmailTemplate + false + unfiled$public/CommunityForgotPasswordEmailTemplate + + Admin + + + Standard + Designer + Designer + Designer + Designer + + examples1 + false + true + examples + NotArchived + UnderConstruction + + home + Chatter + + vforcesite + unfiled$public/CommunityWelcomeEmailTemplate + diff --git a/examples/main/default/pages/AnswersHome.page b/examples/main/default/pages/AnswersHome.page new file mode 100644 index 0000000..a305b75 --- /dev/null +++ b/examples/main/default/pages/AnswersHome.page @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/main/default/pages/AnswersHome.page-meta.xml b/examples/main/default/pages/AnswersHome.page-meta.xml new file mode 100644 index 0000000..e07f895 --- /dev/null +++ b/examples/main/default/pages/AnswersHome.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform home page for Answers sites + + diff --git a/examples/main/default/pages/BandwidthExceeded.page b/examples/main/default/pages/BandwidthExceeded.page new file mode 100644 index 0000000..c6619ce --- /dev/null +++ b/examples/main/default/pages/BandwidthExceeded.page @@ -0,0 +1,13 @@ + + + + + + + +
+
+ +
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/BandwidthExceeded.page-meta.xml b/examples/main/default/pages/BandwidthExceeded.page-meta.xml new file mode 100644 index 0000000..b216a6f --- /dev/null +++ b/examples/main/default/pages/BandwidthExceeded.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform Limit Exceeded page + + diff --git a/examples/main/default/pages/ChangePassword.page b/examples/main/default/pages/ChangePassword.page new file mode 100644 index 0000000..ffb6b69 --- /dev/null +++ b/examples/main/default/pages/ChangePassword.page @@ -0,0 +1,41 @@ + + + +
+ +
+
+ +
+ + + + +
+ +
+ + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/ChangePassword.page-meta.xml b/examples/main/default/pages/ChangePassword.page-meta.xml new file mode 100644 index 0000000..416069b --- /dev/null +++ b/examples/main/default/pages/ChangePassword.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Salesforce Sites Change Password page + + diff --git a/examples/main/default/pages/CommunitiesLanding.page b/examples/main/default/pages/CommunitiesLanding.page new file mode 100644 index 0000000..247a950 --- /dev/null +++ b/examples/main/default/pages/CommunitiesLanding.page @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/examples/main/default/pages/CommunitiesLanding.page-meta.xml b/examples/main/default/pages/CommunitiesLanding.page-meta.xml new file mode 100644 index 0000000..915af51 --- /dev/null +++ b/examples/main/default/pages/CommunitiesLanding.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default experiences landing page + + diff --git a/examples/main/default/pages/CommunitiesLogin.page b/examples/main/default/pages/CommunitiesLogin.page new file mode 100644 index 0000000..ba837c9 --- /dev/null +++ b/examples/main/default/pages/CommunitiesLogin.page @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/examples/main/default/pages/CommunitiesLogin.page-meta.xml b/examples/main/default/pages/CommunitiesLogin.page-meta.xml new file mode 100644 index 0000000..f339d24 --- /dev/null +++ b/examples/main/default/pages/CommunitiesLogin.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default experiences login page + + diff --git a/examples/main/default/pages/CommunitiesSelfReg.page b/examples/main/default/pages/CommunitiesSelfReg.page new file mode 100644 index 0000000..ba08f09 --- /dev/null +++ b/examples/main/default/pages/CommunitiesSelfReg.page @@ -0,0 +1,28 @@ + + +
+ + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
\ No newline at end of file diff --git a/examples/main/default/pages/CommunitiesSelfReg.page-meta.xml b/examples/main/default/pages/CommunitiesSelfReg.page-meta.xml new file mode 100644 index 0000000..b49a43a --- /dev/null +++ b/examples/main/default/pages/CommunitiesSelfReg.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default experiences self registration page + + diff --git a/examples/main/default/pages/CommunitiesSelfRegConfirm.page b/examples/main/default/pages/CommunitiesSelfRegConfirm.page new file mode 100644 index 0000000..cdf230f --- /dev/null +++ b/examples/main/default/pages/CommunitiesSelfRegConfirm.page @@ -0,0 +1,28 @@ + + +
+ +
+
+ +
+ + + + +
+ +
+
+ {!$Label.site.go_to_login_page} +
+
+
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/CommunitiesSelfRegConfirm.page-meta.xml b/examples/main/default/pages/CommunitiesSelfRegConfirm.page-meta.xml new file mode 100644 index 0000000..f9e3376 --- /dev/null +++ b/examples/main/default/pages/CommunitiesSelfRegConfirm.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default experiences self registration confirmation page + + diff --git a/examples/main/default/pages/CommunitiesTemplate.page b/examples/main/default/pages/CommunitiesTemplate.page new file mode 100644 index 0000000..345cb1d --- /dev/null +++ b/examples/main/default/pages/CommunitiesTemplate.page @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/examples/main/default/pages/CommunitiesTemplate.page-meta.xml b/examples/main/default/pages/CommunitiesTemplate.page-meta.xml new file mode 100644 index 0000000..301b18b --- /dev/null +++ b/examples/main/default/pages/CommunitiesTemplate.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default template for experiences pages + + diff --git a/examples/main/default/pages/Exception.page b/examples/main/default/pages/Exception.page new file mode 100644 index 0000000..6a1c44f --- /dev/null +++ b/examples/main/default/pages/Exception.page @@ -0,0 +1,33 @@ + + + +
+ +
+
+ +
+ + + + + + + + + +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/Exception.page-meta.xml b/examples/main/default/pages/Exception.page-meta.xml new file mode 100644 index 0000000..95ba9e2 --- /dev/null +++ b/examples/main/default/pages/Exception.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform page for post-authentication errors + + diff --git a/examples/main/default/pages/FileNotFound.page b/examples/main/default/pages/FileNotFound.page new file mode 100644 index 0000000..de87624 --- /dev/null +++ b/examples/main/default/pages/FileNotFound.page @@ -0,0 +1,31 @@ + + + +
+ +
+
+ +
+ + + + + + + + +
+
+ +
+
+
+ +
+
+
+
+
+ +
\ No newline at end of file diff --git a/examples/main/default/pages/FileNotFound.page-meta.xml b/examples/main/default/pages/FileNotFound.page-meta.xml new file mode 100644 index 0000000..6eb6b45 --- /dev/null +++ b/examples/main/default/pages/FileNotFound.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform Page/Data Not Found page + + diff --git a/examples/main/default/pages/ForgotPassword.page b/examples/main/default/pages/ForgotPassword.page new file mode 100644 index 0000000..9877886 --- /dev/null +++ b/examples/main/default/pages/ForgotPassword.page @@ -0,0 +1,36 @@ + + + +
+ +
+
+ +
+ + + + +
+ +
+ + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/ForgotPassword.page-meta.xml b/examples/main/default/pages/ForgotPassword.page-meta.xml new file mode 100644 index 0000000..e20a0c1 --- /dev/null +++ b/examples/main/default/pages/ForgotPassword.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Salesforce Sites Forgot Password Confirmation page + + diff --git a/examples/main/default/pages/ForgotPasswordConfirm.page b/examples/main/default/pages/ForgotPasswordConfirm.page new file mode 100644 index 0000000..77f2654 --- /dev/null +++ b/examples/main/default/pages/ForgotPasswordConfirm.page @@ -0,0 +1,30 @@ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+ {!$Label.site.go_to_login_page} +
+
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/ForgotPasswordConfirm.page-meta.xml b/examples/main/default/pages/ForgotPasswordConfirm.page-meta.xml new file mode 100644 index 0000000..7240002 --- /dev/null +++ b/examples/main/default/pages/ForgotPasswordConfirm.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Salesforce Sites Forgot Password Confirmation page + + diff --git a/examples/main/default/pages/IdeasHome.page b/examples/main/default/pages/IdeasHome.page new file mode 100644 index 0000000..165405b --- /dev/null +++ b/examples/main/default/pages/IdeasHome.page @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/main/default/pages/IdeasHome.page-meta.xml b/examples/main/default/pages/IdeasHome.page-meta.xml new file mode 100644 index 0000000..f3d6d40 --- /dev/null +++ b/examples/main/default/pages/IdeasHome.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform home page for ideas sites + + diff --git a/examples/main/default/pages/InMaintenance.page b/examples/main/default/pages/InMaintenance.page new file mode 100644 index 0000000..a0f5f0f --- /dev/null +++ b/examples/main/default/pages/InMaintenance.page @@ -0,0 +1,15 @@ + + + + + + + + + +
+
+ +
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/InMaintenance.page-meta.xml b/examples/main/default/pages/InMaintenance.page-meta.xml new file mode 100644 index 0000000..b936fda --- /dev/null +++ b/examples/main/default/pages/InMaintenance.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform In Maintenance page + + diff --git a/examples/main/default/pages/MicrobatchSelfReg.page b/examples/main/default/pages/MicrobatchSelfReg.page new file mode 100644 index 0000000..96b3069 --- /dev/null +++ b/examples/main/default/pages/MicrobatchSelfReg.page @@ -0,0 +1,24 @@ + + +
+ + + + + + + + + + + + + + +
+
+
+
+
+ +
\ No newline at end of file diff --git a/examples/main/default/pages/MicrobatchSelfReg.page-meta.xml b/examples/main/default/pages/MicrobatchSelfReg.page-meta.xml new file mode 100644 index 0000000..25eb82b --- /dev/null +++ b/examples/main/default/pages/MicrobatchSelfReg.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Process self-registration requests in batches instead of individually + + diff --git a/examples/main/default/pages/MyProfilePage.page b/examples/main/default/pages/MyProfilePage.page new file mode 100644 index 0000000..bf72930 --- /dev/null +++ b/examples/main/default/pages/MyProfilePage.page @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/main/default/pages/MyProfilePage.page-meta.xml b/examples/main/default/pages/MyProfilePage.page-meta.xml new file mode 100644 index 0000000..dc6c56f --- /dev/null +++ b/examples/main/default/pages/MyProfilePage.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Salesforce Sites My Profile page + + diff --git a/examples/main/default/pages/SiteLogin.page b/examples/main/default/pages/SiteLogin.page new file mode 100644 index 0000000..6f0900b --- /dev/null +++ b/examples/main/default/pages/SiteLogin.page @@ -0,0 +1,29 @@ + + + +
+ +
+
+ +
+ + + + +
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/SiteLogin.page-meta.xml b/examples/main/default/pages/SiteLogin.page-meta.xml new file mode 100644 index 0000000..cfb8f57 --- /dev/null +++ b/examples/main/default/pages/SiteLogin.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Salesforce Sites Login page + + diff --git a/examples/main/default/pages/SiteRegister.page b/examples/main/default/pages/SiteRegister.page new file mode 100644 index 0000000..b4e443e --- /dev/null +++ b/examples/main/default/pages/SiteRegister.page @@ -0,0 +1,45 @@ + + + +
+ +
+
+ +
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/SiteRegister.page-meta.xml b/examples/main/default/pages/SiteRegister.page-meta.xml new file mode 100644 index 0000000..b01800f --- /dev/null +++ b/examples/main/default/pages/SiteRegister.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Salesforce Sites User Registration page + + diff --git a/examples/main/default/pages/SiteRegisterConfirm.page b/examples/main/default/pages/SiteRegisterConfirm.page new file mode 100644 index 0000000..6001957 --- /dev/null +++ b/examples/main/default/pages/SiteRegisterConfirm.page @@ -0,0 +1,30 @@ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+ {!$Label.site.go_to_login_page} +
+
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/SiteRegisterConfirm.page-meta.xml b/examples/main/default/pages/SiteRegisterConfirm.page-meta.xml new file mode 100644 index 0000000..3fda7c1 --- /dev/null +++ b/examples/main/default/pages/SiteRegisterConfirm.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Salesforce Sites User Registration Confirmation page + + diff --git a/examples/main/default/pages/SiteTemplate.page b/examples/main/default/pages/SiteTemplate.page new file mode 100644 index 0000000..2476eb5 --- /dev/null +++ b/examples/main/default/pages/SiteTemplate.page @@ -0,0 +1,13 @@ + + + + +
+
+ + +
+ + +
+
\ No newline at end of file diff --git a/examples/main/default/pages/SiteTemplate.page-meta.xml b/examples/main/default/pages/SiteTemplate.page-meta.xml new file mode 100644 index 0000000..bbf7815 --- /dev/null +++ b/examples/main/default/pages/SiteTemplate.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform template for site pages + + diff --git a/examples/main/default/pages/StdExceptionTemplate.page b/examples/main/default/pages/StdExceptionTemplate.page new file mode 100644 index 0000000..965917d --- /dev/null +++ b/examples/main/default/pages/StdExceptionTemplate.page @@ -0,0 +1,23 @@ + + +
+ +
+
+ +
+ + + + + + + + +
+
+ +
+
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/StdExceptionTemplate.page-meta.xml b/examples/main/default/pages/StdExceptionTemplate.page-meta.xml new file mode 100644 index 0000000..ba17804 --- /dev/null +++ b/examples/main/default/pages/StdExceptionTemplate.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform template for standard exception pages + + diff --git a/examples/main/default/pages/Unauthorized.page b/examples/main/default/pages/Unauthorized.page new file mode 100644 index 0000000..b0e4ff2 --- /dev/null +++ b/examples/main/default/pages/Unauthorized.page @@ -0,0 +1,38 @@ + + + +
+ +
+
+ +
+ + + + + + +
+
+ +
+ +
+
+
+ + + +
+
+
+ +
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/examples/main/default/pages/Unauthorized.page-meta.xml b/examples/main/default/pages/Unauthorized.page-meta.xml new file mode 100644 index 0000000..980b0ed --- /dev/null +++ b/examples/main/default/pages/Unauthorized.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform Authorization Required page + + diff --git a/examples/main/default/pages/UnderConstruction.page b/examples/main/default/pages/UnderConstruction.page new file mode 100644 index 0000000..81dc16e --- /dev/null +++ b/examples/main/default/pages/UnderConstruction.page @@ -0,0 +1,15 @@ + + + + + + + + + +
+
+ +
+
+
\ No newline at end of file diff --git a/examples/main/default/pages/UnderConstruction.page-meta.xml b/examples/main/default/pages/UnderConstruction.page-meta.xml new file mode 100644 index 0000000..301e19a --- /dev/null +++ b/examples/main/default/pages/UnderConstruction.page-meta.xml @@ -0,0 +1,8 @@ + + + 60.0 + false + false + Default Lightning Platform Under Construction page + + diff --git a/examples/main/default/profiles/Admin.profile-meta.xml b/examples/main/default/profiles/Admin.profile-meta.xml new file mode 100644 index 0000000..0228dbb --- /dev/null +++ b/examples/main/default/profiles/Admin.profile-meta.xml @@ -0,0 +1,949 @@ + + + + ChangePasswordController + true + + + ChangePasswordControllerTest + true + + + CommunitiesLandingController + true + + + CommunitiesLandingControllerTest + true + + + CommunitiesLoginController + true + + + CommunitiesLoginControllerTest + true + + + CommunitiesSelfRegConfirmController + true + + + CommunitiesSelfRegConfirmControllerTest + true + + + CommunitiesSelfRegController + true + + + CommunitiesSelfRegControllerTest + true + + + ForgotPasswordController + true + + + ForgotPasswordControllerTest + true + + + LightningForgotPasswordController + true + + + LightningForgotPasswordControllerTest + true + + + LightningLoginFormController + true + + + LightningLoginFormControllerTest + true + + + LightningSelfRegisterController + true + + + LightningSelfRegisterControllerTest + true + + + MicrobatchSelfRegController + true + + + MicrobatchSelfRegControllerTest + true + + + MyProfilePageController + true + + + MyProfilePageControllerTest + true + + + ResourceController + true + + + ShoppingCartController + true + + + SiteLoginController + true + + + SiteLoginControllerTest + true + + + SiteRegisterController + true + + + SiteRegisterControllerTest + true + + false + + AnswersHome + true + + + BandwidthExceeded + true + + + ChangePassword + true + + + CommunitiesLanding + true + + + CommunitiesLogin + true + + + CommunitiesSelfReg + true + + + CommunitiesSelfRegConfirm + true + + + CommunitiesTemplate + true + + + Exception + true + + + FileNotFound + true + + + ForgotPassword + true + + + ForgotPasswordConfirm + true + + + IdeasHome + true + + + InMaintenance + true + + + MicrobatchSelfReg + true + + + MyProfilePage + true + + + SiteLogin + true + + + SiteRegister + true + + + SiteRegisterConfirm + true + + + SiteTemplate + true + + + StdExceptionTemplate + true + + + Unauthorized + true + + + UnderConstruction + true + + Salesforce + + true + AIViewInsightObjects + + + true + ActivateContract + + + true + ActivateOrder + + + true + ActivitiesAccess + + + true + AddDirectMessageMembers + + + true + AllowUniversalSearch + + + true + AllowViewKnowledge + + + true + ApexRestServices + + + true + ApiEnabled + + + true + AssignPermissionSets + + + true + AssignTopics + + + true + AuthorApex + + + true + BulkMacrosAllowed + + + true + CanAccessCE + + + true + CanInsertFeedSystemFields + + + true + CanUseNewDashboardBuilder + + + true + CanVerifyComment + + + true + ChangeDashboardColors + + + true + ChatterEditOwnPost + + + true + ChatterEditOwnRecordPost + + + true + ChatterFileLink + + + true + ChatterInternalUser + + + true + ChatterInviteExternalUsers + + + true + ChatterOwnGroups + + + true + ClientSecretRotation + + + true + ConnectOrgToEnvironmentHub + + + true + ConsentApiUpdate + + + true + ContentAdministrator + + + true + ContentWorkspaces + + + true + ConvertLeads + + + true + CreateCustomizeDashboards + + + true + CreateCustomizeFilters + + + true + CreateCustomizeReports + + + true + CreateDashboardFolders + + + true + CreateLtngTempFolder + + + true + CreateReportFolders + + + true + CreateTopics + + + true + CreateWorkBadgeDefinition + + + true + CreateWorkspaces + + + true + CustomizeApplication + + + true + DataExport + + + true + DelegatedTwoFactor + + + true + DeleteActivatedContract + + + true + DeleteTopics + + + true + DistributeFromPersWksp + + + true + EditActivatedOrders + + + true + EditBillingInfo + + + true + EditBrandTemplates + + + true + EditCaseComments + + + true + EditEvent + + + true + EditHtmlTemplates + + + true + EditKnowledge + + + true + EditMyDashboards + + + true + EditMyReports + + + true + EditOppLineItemUnitPrice + + + true + EditPublicDocuments + + + true + EditPublicFilters + + + true + EditPublicTemplates + + + true + EditReadonlyFields + + + true + EditTask + + + true + EditTopics + + + true + EmailMass + + + true + EmailSingle + + + true + EnableCommunityAppLauncher + + + true + EnableNotifications + + + true + ExportReport + + + true + FieldServiceAccess + + + true + GiveRecognitionBadge + + + true + ImportCustomObjects + + + true + ImportLeads + + + true + ImportPersonal + + + true + InstallPackaging + + + true + LightningConsoleAllowedForUser + + + true + LightningExperienceUser + + + true + ListEmailSend + + + true + ManageAnalyticSnapshots + + + true + ManageAuthProviders + + + true + ManageBusinessHourHolidays + + + true + ManageC360AConnections + + + true + ManageCMS + + + true + ManageCallCenters + + + true + ManageCases + + + true + ManageCategories + + + true + ManageCertificates + + + true + ManageContentPermissions + + + true + ManageContentProperties + + + true + ManageContentTypes + + + true + ManageCustomPermissions + + + true + ManageCustomReportTypes + + + true + ManageDashbdsInPubFolders + + + true + ManageDataCategories + + + true + ManageDataIntegrations + + + true + ManageDataMaskPolicies + + + true + ManageDynamicDashboards + + + true + ManageEmailClientConfig + + + true + ManageEntitlements + + + true + ManageExchangeConfig + + + true + ManageHealthCheck + + + true + ManageHubConnections + + + true + ManageInteraction + + + true + ManageInternalUsers + + + true + ManageIpAddresses + + + true + ManageKnowledge + + + true + ManageKnowledgeImportExport + + + true + ManageLeads + + + true + ManageLoginAccessPolicies + + + true + ManageMobile + + + true + ManageNetworks + + + true + ManageOrchInstsAndWorkItems + + + true + ManagePackageLicenses + + + true + ManagePartners + + + true + ManagePasswordPolicies + + + true + ManageProfilesPermissionsets + + + true + ManagePropositions + + + true + ManagePvtRptsAndDashbds + + + true + ManageRecommendationStrategies + + + true + ManageReleaseUpdates + + + true + ManageRemoteAccess + + + true + ManageReportsInPubFolders + + + true + ManageRoles + + + true + ManageSearchPromotionRules + + + true + ManageSharing + + + true + ManageSolutions + + + true + ManageSubscriptions + + + true + ManageSynonyms + + + true + ManageUnlistedGroups + + + true + ManageUsers + + + true + MassInlineEdit + + + true + MergeTopics + + + true + ModerateChatter + + + true + ModifyAllData + + + true + ModifyDataClassification + + + true + ModifyMetadata + + + true + NewReportBuilder + + + true + OmnichannelInventorySync + + + true + Packaging2 + + + true + Packaging2Delete + + + true + PrivacyDataAccess + + + true + RemoveDirectMessageMembers + + + true + ResetPasswords + + + true + RunReports + + + true + ScheduleReports + + + true + SelectFilesFromSalesforce + + + true + SendCustomNotifications + + + true + SendExternalEmailAvailable + + + true + SendSitRequests + + + true + ShareFilesWithNetworks + + + true + ShareInternalArticles + + + true + ShowCompanyNameAsUserBadge + + + true + SolutionImport + + + true + SubmitMacrosAllowed + + + true + SubscribeDashboardRolesGrps + + + true + SubscribeDashboardToOtherUsers + + + true + SubscribeReportRolesGrps + + + true + SubscribeReportToOtherUsers + + + true + SubscribeReportsRunAsUser + + + true + SubscribeToLightningDashboards + + + true + SubscribeToLightningReports + + + true + TransactionalEmailSend + + + true + TransferAnyCase + + + true + TransferAnyEntity + + + true + TransferAnyLead + + + true + UseOmnichannelInventoryAPIs + + + true + UseTeamReassignWizards + + + true + UseWebLink + + + true + ViewAllData + + + true + ViewAllProfiles + + + true + ViewAllUsers + + + true + ViewDataAssessment + + + true + ViewDataCategories + + + true + ViewDataLeakageEvents + + + true + ViewDeveloperName + + + true + ViewEventLogFiles + + + true + ViewFlowUsageAndFlowEventData + + + true + ViewHealthCheck + + + true + ViewHelpLink + + + true + ViewMLModels + + + true + ViewMyTeamsDashboards + + + true + ViewPlatformEvents + + + true + ViewPublicDashboards + + + true + ViewPublicReports + + + true + ViewRoles + + + true + ViewSetup + + + true + ViewUserPII + + + true + WorkCalibrationUser + + diff --git a/examples/main/default/profiles/examples Profile.profile-meta.xml b/examples/main/default/profiles/examples Profile.profile-meta.xml new file mode 100644 index 0000000..4581d68 --- /dev/null +++ b/examples/main/default/profiles/examples Profile.profile-meta.xml @@ -0,0 +1,209 @@ + + + + ChangePasswordController + false + + + ChangePasswordControllerTest + false + + + CommunitiesLandingController + false + + + CommunitiesLandingControllerTest + false + + + CommunitiesLoginController + false + + + CommunitiesLoginControllerTest + false + + + CommunitiesSelfRegConfirmController + false + + + CommunitiesSelfRegConfirmControllerTest + false + + + CommunitiesSelfRegController + false + + + CommunitiesSelfRegControllerTest + false + + + ForgotPasswordController + false + + + ForgotPasswordControllerTest + false + + + LightningForgotPasswordController + false + + + LightningForgotPasswordControllerTest + false + + + LightningLoginFormController + false + + + LightningLoginFormControllerTest + false + + + LightningSelfRegisterController + false + + + LightningSelfRegisterControllerTest + false + + + MicrobatchSelfRegController + false + + + MicrobatchSelfRegControllerTest + false + + + MyProfilePageController + false + + + MyProfilePageControllerTest + false + + + ResourceController + false + + + ShoppingCartController + false + + + SiteLoginController + false + + + SiteLoginControllerTest + false + + + SiteRegisterController + false + + + SiteRegisterControllerTest + false + + true + + AnswersHome + false + + + BandwidthExceeded + true + + + ChangePassword + false + + + CommunitiesLanding + true + + + CommunitiesLogin + true + + + CommunitiesSelfReg + true + + + CommunitiesSelfRegConfirm + true + + + CommunitiesTemplate + true + + + Exception + true + + + FileNotFound + true + + + ForgotPassword + true + + + ForgotPasswordConfirm + true + + + IdeasHome + false + + + InMaintenance + true + + + MicrobatchSelfReg + true + + + MyProfilePage + false + + + SiteLogin + true + + + SiteRegister + true + + + SiteRegisterConfirm + true + + + SiteTemplate + false + + + StdExceptionTemplate + false + + + Unauthorized + false + + + UnderConstruction + true + + Guest User License + diff --git a/examples/main/default/sites/examples.site-meta.xml b/examples/main/default/sites/examples.site-meta.xml new file mode 100644 index 0000000..3dfab52 --- /dev/null +++ b/examples/main/default/sites/examples.site-meta.xml @@ -0,0 +1,30 @@ + + + true + false + false + false + false + false + true + false + CommunitiesLogin + BandwidthExceeded + true + true + SameOriginOnly + true + true + FileNotFound + Exception + InMaintenance + CommunitiesLanding + examples + false + true + CommunitiesSelfReg + test-aihaub8gzvvn@example.com + test-aihaub8gzvvn@example.com + ChatterNetwork + vforcesite + diff --git a/examples/main/default/staticresources/SiteSamples.resource-meta.xml b/examples/main/default/staticresources/SiteSamples.resource-meta.xml new file mode 100644 index 0000000..1ed30e1 --- /dev/null +++ b/examples/main/default/staticresources/SiteSamples.resource-meta.xml @@ -0,0 +1,6 @@ + + + Public + application/zip + Static resource for sites sample pages + diff --git a/examples/main/default/staticresources/SiteSamples/SiteStyles.css b/examples/main/default/staticresources/SiteSamples/SiteStyles.css new file mode 100644 index 0000000..c61cbc0 --- /dev/null +++ b/examples/main/default/staticresources/SiteSamples/SiteStyles.css @@ -0,0 +1,28 @@ +.topPanelContainer { + text-align:left; + border:1px solid #ccc; +} + +.topPanel { + background-color: white; + border: 1px solid #ccc; + padding: 0px; + margin-top: 10px; + margin-bottom: 0px; + margin-left: 10px; + margin-right: 10px; +} + +.title { + font-size: larger; + font-weight: bold; +} + +.poweredByImage { + vertical-align: middle; + margin:12px 8px 8px 0; +} + +img { + border: none; +} \ No newline at end of file diff --git a/examples/main/default/staticresources/SiteSamples/img/clock.png b/examples/main/default/staticresources/SiteSamples/img/clock.png new file mode 100644 index 0000000..17d6749 Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/clock.png differ diff --git a/examples/main/default/staticresources/SiteSamples/img/construction.png b/examples/main/default/staticresources/SiteSamples/img/construction.png new file mode 100644 index 0000000..236ead9 Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/construction.png differ diff --git a/examples/main/default/staticresources/SiteSamples/img/force_logo.png b/examples/main/default/staticresources/SiteSamples/img/force_logo.png new file mode 100644 index 0000000..6ba12db Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/force_logo.png differ diff --git a/examples/main/default/staticresources/SiteSamples/img/maintenance.png b/examples/main/default/staticresources/SiteSamples/img/maintenance.png new file mode 100644 index 0000000..45b636a Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/maintenance.png differ diff --git a/examples/main/default/staticresources/SiteSamples/img/poweredby.png b/examples/main/default/staticresources/SiteSamples/img/poweredby.png new file mode 100644 index 0000000..b2b4f28 Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/poweredby.png differ diff --git a/examples/main/default/staticresources/SiteSamples/img/tools.png b/examples/main/default/staticresources/SiteSamples/img/tools.png new file mode 100644 index 0000000..44a3ee1 Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/tools.png differ diff --git a/examples/main/default/staticresources/SiteSamples/img/unauthorized.png b/examples/main/default/staticresources/SiteSamples/img/unauthorized.png new file mode 100644 index 0000000..1b93e9b Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/unauthorized.png differ diff --git a/examples/main/default/staticresources/SiteSamples/img/warning.png b/examples/main/default/staticresources/SiteSamples/img/warning.png new file mode 100644 index 0000000..325ac99 Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/warning.png differ diff --git a/examples/main/default/staticresources/tw.resource-meta.xml b/examples/main/default/staticresources/tw.resource-meta.xml new file mode 100644 index 0000000..54184e4 --- /dev/null +++ b/examples/main/default/staticresources/tw.resource-meta.xml @@ -0,0 +1,6 @@ + + + Private + application/zip + tw + diff --git a/examples/main/default/staticresources/tw/css/main.css b/examples/main/default/staticresources/tw/css/main.css new file mode 100644 index 0000000..a4cf594 --- /dev/null +++ b/examples/main/default/staticresources/tw/css/main.css @@ -0,0 +1 @@ +/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border-width:0!important}.pointer-events-none{pointer-events:none!important}.pointer-events-auto{pointer-events:auto!important}.fixed{position:fixed!important}.relative{position:relative!important}.inset-0{inset:0!important}.-my-4{margin-top:-1rem!important;margin-bottom:-1rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.ml-3{margin-left:.75rem!important}.ml-4{margin-left:1rem!important}.mt-1{margin-top:.25rem!important}.mt-10{margin-top:2.5rem!important}.mt-2{margin-top:.5rem!important}.mt-4{margin-top:1rem!important}.mt-6{margin-top:1.5rem!important}.block{display:block!important}.flex{display:flex!important}.inline-flex{display:inline-flex!important}.flow-root{display:flow-root!important}.h-12{height:3rem!important}.h-24{height:6rem!important}.h-5{height:1.25rem!important}.w-0{width:0!important}.w-12{width:3rem!important}.w-24{width:6rem!important}.w-5{width:1.25rem!important}.w-full{width:100%!important}.max-w-4xl{max-width:56rem!important}.max-w-sm{max-width:24rem!important}.flex-1{flex:1 1 0%!important}.flex-shrink-0{flex-shrink:0!important}.translate-y-0{--tw-translate-y:0px!important}.translate-y-0,.translate-y-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-2{--tw-translate-y:0.5rem!important}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.flex-col{flex-direction:column!important}.items-end{align-items:flex-end!important}.items-center{align-items:center!important}.justify-center{justify-content:center!important}.justify-between{justify-content:space-between!important}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0!important;margin-right:calc(.5rem*var(--tw-space-x-reverse))!important;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))!important}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(1rem*var(--tw-space-y-reverse))!important}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0!important;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))!important;border-bottom-width:calc(1px*var(--tw-divide-y-reverse))!important}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1!important;border-color:rgb(229 231 235/var(--tw-divide-opacity))!important}.overflow-hidden{overflow:hidden!important}.rounded-lg{border-radius:.5rem!important}.rounded-md{border-radius:.375rem!important}.border{border-width:1px!important}.border-2{border-width:2px!important}.border-b{border-bottom-width:1px!important}.border-t{border-top-width:1px!important}.border-dashed{border-style:dashed!important}.border-gray-200{--tw-border-opacity:1!important;border-color:rgb(229 231 235/var(--tw-border-opacity))!important}.border-gray-300{--tw-border-opacity:1!important;border-color:rgb(209 213 219/var(--tw-border-opacity))!important}.border-transparent{border-color:#0000!important}.bg-gray-50{--tw-bg-opacity:1!important;background-color:rgb(249 250 251/var(--tw-bg-opacity))!important}.bg-indigo-600{background-color:rgb(79 70 229/var(--tw-bg-opacity))!important}.bg-indigo-600,.bg-white{--tw-bg-opacity:1!important}.bg-white{background-color:rgb(255 255 255/var(--tw-bg-opacity))!important}.object-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-center{-o-object-position:center!important;object-position:center!important}.p-12{padding:3rem!important}.p-4{padding:1rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.py-6{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.pb-16{padding-bottom:4rem!important}.pr-6{padding-right:1.5rem!important}.text-center{text-align:center!important}.text-right{text-align:right!important}.text-3xl{font-size:1.875rem!important;line-height:2.25rem!important}.text-base{font-size:1rem!important;line-height:1.5rem!important}.text-sm{font-size:.875rem!important;line-height:1.25rem!important}.font-bold{font-weight:700!important}.font-medium{font-weight:500!important}.font-semibold{font-weight:600!important}.tracking-tight{letter-spacing:-.025em!important}.text-gray-400{--tw-text-opacity:1!important;color:rgb(156 163 175/var(--tw-text-opacity))!important}.text-gray-500{--tw-text-opacity:1!important;color:rgb(107 114 128/var(--tw-text-opacity))!important}.text-gray-600{--tw-text-opacity:1!important;color:rgb(75 85 99/var(--tw-text-opacity))!important}.text-gray-700{--tw-text-opacity:1!important;color:rgb(55 65 81/var(--tw-text-opacity))!important}.text-gray-900{--tw-text-opacity:1!important;color:rgb(17 24 39/var(--tw-text-opacity))!important}.text-green-500{--tw-text-opacity:1!important;color:rgb(34 197 94/var(--tw-text-opacity))!important}.text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.text-white{--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}.opacity-0{opacity:0!important}.opacity-100{opacity:1!important}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a!important;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)!important}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d!important;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)!important}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)!important;--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)!important;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)!important}.ring-black{--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))!important}.ring-opacity-5{--tw-ring-opacity:0.05!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important;transition-duration:.15s!important}.duration-100{transition-duration:.1s!important}.duration-300{transition-duration:.3s!important}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)!important}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)!important}.hover\:border-gray-400:hover{--tw-border-opacity:1!important;border-color:rgb(156 163 175/var(--tw-border-opacity))!important}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1!important;background-color:rgb(67 56 202/var(--tw-bg-opacity))!important}.hover\:text-gray-500:hover{--tw-text-opacity:1!important;color:rgb(107 114 128/var(--tw-text-opacity))!important}.hover\:text-gray-800:hover{--tw-text-opacity:1!important;color:rgb(31 41 55/var(--tw-text-opacity))!important}.hover\:text-indigo-500:hover{--tw-text-opacity:1!important;color:rgb(99 102 241/var(--tw-text-opacity))!important}.focus\:outline-none:focus{outline:2px solid #0000!important;outline-offset:2px!important}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)!important;--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)!important;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)!important}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1!important;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))!important}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px!important}.focus\:ring-offset-gray-50:focus{--tw-ring-offset-color:#f9fafb!important}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed!important}.disabled\:opacity-50:disabled{opacity:.5!important}@media (min-width:640px){.sm\:absolute{position:absolute!important}.sm\:left-1\/2{left:50%!important}.sm\:top-0{top:0!important}.sm\:ml-0{margin-left:0!important}.sm\:ml-6{margin-left:1.5rem!important}.sm\:mt-0{margin-top:0!important}.sm\:mt-3{margin-top:.75rem!important}.sm\:block{display:block!important}.sm\:grid{display:grid!important}.sm\:h-32{height:8rem!important}.sm\:w-32{width:8rem!important}.sm\:translate-x-0{--tw-translate-x:0px!important}.sm\:translate-x-0,.sm\:translate-x-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.sm\:translate-x-2{--tw-translate-x:0.5rem!important}.sm\:translate-y-0{--tw-translate-y:0px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.sm\:items-start{align-items:flex-start!important}.sm\:items-end{align-items:flex-end!important}.sm\:p-6{padding:1.5rem!important}.sm\:py-10{padding-top:2.5rem!important;padding-bottom:2.5rem!important}}@media (min-width:1024px){.lg\:p-8{padding:2rem!important}} \ No newline at end of file diff --git a/examples/server-communication/classes/ResourceController.cls b/examples/server-communication/classes/ResourceController.cls index dbe1323..1694193 100644 --- a/examples/server-communication/classes/ResourceController.cls +++ b/examples/server-communication/classes/ResourceController.cls @@ -14,6 +14,7 @@ public without sharing class ResourceController { @AuraEnabled(Cacheable=true) public static List getContacts() { + System.debug('getContacts() called'); return [SELECT Id, Name FROM Contact]; } } diff --git a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.html b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.html index c73d5d6..9653fa3 100644 --- a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.html +++ b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.html @@ -14,4 +14,4 @@

Selected Account

- + \ No newline at end of file diff --git a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js index c2a38ce..665543c 100644 --- a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js +++ b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js @@ -4,4 +4,4 @@ import { getAccount } from "c/demoSignals"; export default class DisplaySelectedAccount extends LightningElement { account = $computed(() => (this.account = getAccount.value)).value; -} +} \ No newline at end of file diff --git a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js-meta.xml b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js-meta.xml index 7899a89..b2680f6 100644 --- a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js-meta.xml +++ b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js-meta.xml @@ -8,4 +8,4 @@ lightningCommunity__Default lightningCommunity__Page - + \ No newline at end of file diff --git a/examples/server-communication/lwc/listAccounts/listAccounts.html b/examples/server-communication/lwc/listAccounts/listAccounts.html index 18f66f8..78d93b7 100644 --- a/examples/server-communication/lwc/listAccounts/listAccounts.html +++ b/examples/server-communication/lwc/listAccounts/listAccounts.html @@ -6,4 +6,4 @@ options={accounts} onchange={handleAccountChange} > - + \ No newline at end of file diff --git a/examples/server-communication/lwc/listAccounts/listAccounts.js b/examples/server-communication/lwc/listAccounts/listAccounts.js index a885e4e..eedb476 100644 --- a/examples/server-communication/lwc/listAccounts/listAccounts.js +++ b/examples/server-communication/lwc/listAccounts/listAccounts.js @@ -28,4 +28,4 @@ export default class ListAccounts extends LightningElement { handleAccountChange(event) { selectedAccountId.value = event.detail.value; } -} +} \ No newline at end of file diff --git a/examples/server-communication/lwc/listAccounts/listAccounts.js-meta.xml b/examples/server-communication/lwc/listAccounts/listAccounts.js-meta.xml index 147d9cb..64e404d 100644 --- a/examples/server-communication/lwc/listAccounts/listAccounts.js-meta.xml +++ b/examples/server-communication/lwc/listAccounts/listAccounts.js-meta.xml @@ -8,4 +8,4 @@ lightningCommunity__Default lightningCommunity__Page - + \ No newline at end of file diff --git a/examples/server-communication/lwc/serverFetcher/serverFetcher.html b/examples/server-communication/lwc/serverFetcher/serverFetcher.html index f60a6c2..4b4d549 100644 --- a/examples/server-communication/lwc/serverFetcher/serverFetcher.html +++ b/examples/server-communication/lwc/serverFetcher/serverFetcher.html @@ -10,4 +10,4 @@ - + \ No newline at end of file diff --git a/examples/server-communication/lwc/serverFetcher/serverFetcher.js b/examples/server-communication/lwc/serverFetcher/serverFetcher.js index 3ed29d6..4c2eb48 100644 --- a/examples/server-communication/lwc/serverFetcher/serverFetcher.js +++ b/examples/server-communication/lwc/serverFetcher/serverFetcher.js @@ -4,4 +4,4 @@ import { fetchContacts } from "c/demoSignals"; export default class ServerFetcher extends LightningElement { contacts = $computed(() => (this.contacts = fetchContacts.value)).value; -} +} \ No newline at end of file diff --git a/examples/server-communication/lwc/serverFetcher/serverFetcher.js-meta.xml b/examples/server-communication/lwc/serverFetcher/serverFetcher.js-meta.xml index c7b87e6..9866124 100644 --- a/examples/server-communication/lwc/serverFetcher/serverFetcher.js-meta.xml +++ b/examples/server-communication/lwc/serverFetcher/serverFetcher.js-meta.xml @@ -8,4 +8,4 @@ lightningCommunity__Default lightningCommunity__Page - + \ No newline at end of file diff --git a/examples/shopping-cart/controllers/ShoppingCartController.cls b/examples/shopping-cart/controllers/ShoppingCartController.cls new file mode 100644 index 0000000..327688b --- /dev/null +++ b/examples/shopping-cart/controllers/ShoppingCartController.cls @@ -0,0 +1,50 @@ +public with sharing class ShoppingCartController { + @AuraEnabled(Cacheable=true) + public static ShoppingCartDto getShoppingCart() { + return new ShoppingCartDto(buildSampleItemList()); + } + + @AuraEnabled + public static ShoppingCartDto updateShoppingCart(List newItems) { + // This is just for example purpose, in a real-world scenario, you would update the shopping cart in the database + return new ShoppingCartDto(newItems); + } + + private static List buildSampleItemList() { + List items = new List(); + items.add(new ItemDto('1', 'Nomad Tumbler', new List{'White'}, 1, 35.0, 'https://tailwindui.com/img/ecommerce-images/shopping-cart-page-01-product-03.jpg')); + items.add(new ItemDto('2', 'Basic Tee', new List{'Sienna', 'Large'}, 1, 32.0, 'https://tailwindui.com/img/ecommerce-images/shopping-cart-page-01-product-01.jpg')); + return items; + } + + private static final Decimal TAX_RATE = 0.1; + public class ItemDto { + @AuraEnabled public String id { get; set; } + @AuraEnabled public String name { get; set; } + @AuraEnabled public List properties { get; set; } + @AuraEnabled public Integer quantity { get; set; } + @AuraEnabled public Decimal price { get; set; } + @AuraEnabled public Decimal taxAmount { get {return (price * quantity) * TAX_RATE;} } + @AuraEnabled public String imgUrl { get; set; } + + // No arg constructor so it can be built from JSON + public ItemDto() {} + + public ItemDto(String id, String name, List properties, Integer quantity, Decimal price, String imgUrl) { + this.id = id; + this.name = name; + this.properties = properties; + this.quantity = quantity; + this.price = price; + this.imgUrl = imgUrl; + } + } + + public class ShoppingCartDto { + @AuraEnabled public List items; + + public ShoppingCartDto(List items) { + this.items = items; + } + } +} diff --git a/examples/shopping-cart/controllers/ShoppingCartController.cls-meta.xml b/examples/shopping-cart/controllers/ShoppingCartController.cls-meta.xml new file mode 100644 index 0000000..f5e18fd --- /dev/null +++ b/examples/shopping-cart/controllers/ShoppingCartController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/examples/shopping-cart/lwc/checkoutButton/checkoutButton.html b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.html new file mode 100644 index 0000000..41a40c8 --- /dev/null +++ b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js new file mode 100644 index 0000000..209f871 --- /dev/null +++ b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js @@ -0,0 +1,19 @@ +import TwElement from "c/twElement"; +import {$computed} from 'c/signals'; +import {shoppingCart} from "c/demoSignals"; + +// States +import ready from "./states/ready.html"; +import loading from "./states/loading.html"; + +export default class CheckoutButton extends TwElement { + itemData = $computed(() => this.itemData = shoppingCart.value).value; + + render() { + return this.itemData.loading ? loading : ready; + } + + get isEmpty() { + return this.itemData.data.items.length === 0; + } +} \ No newline at end of file diff --git a/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js-meta.xml b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js-meta.xml new file mode 100644 index 0000000..ec3011f --- /dev/null +++ b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js-meta.xml @@ -0,0 +1,11 @@ + + + 60.0 + Checkout Button + true + Checkout Button + + lightningCommunity__Default + lightningCommunity__Page + + \ No newline at end of file diff --git a/examples/shopping-cart/lwc/checkoutButton/states/loading.html b/examples/shopping-cart/lwc/checkoutButton/states/loading.html new file mode 100644 index 0000000..c985c2f --- /dev/null +++ b/examples/shopping-cart/lwc/checkoutButton/states/loading.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/examples/shopping-cart/lwc/checkoutButton/states/ready.html b/examples/shopping-cart/lwc/checkoutButton/states/ready.html new file mode 100644 index 0000000..81b4d39 --- /dev/null +++ b/examples/shopping-cart/lwc/checkoutButton/states/ready.html @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/examples/shopping-cart/lwc/orderSummary/orderSummary.html b/examples/shopping-cart/lwc/orderSummary/orderSummary.html new file mode 100644 index 0000000..cc340bc --- /dev/null +++ b/examples/shopping-cart/lwc/orderSummary/orderSummary.html @@ -0,0 +1 @@ + diff --git a/examples/shopping-cart/lwc/orderSummary/orderSummary.js b/examples/shopping-cart/lwc/orderSummary/orderSummary.js new file mode 100644 index 0000000..9f8bb92 --- /dev/null +++ b/examples/shopping-cart/lwc/orderSummary/orderSummary.js @@ -0,0 +1,43 @@ +import TwElement from "c/twElement"; +import { $computed } from "c/signals"; +import { shoppingCart } from "c/demoSignals"; + +// States +import ready from "./states/ready.html"; +import loading from "./states/loading.html"; +import empty from "./states/empty.html"; + +export default class OrderSummary extends TwElement { + itemData = $computed(() => (this.itemData = shoppingCart.value)).value; + + render() { + return this.itemData.loading + ? loading + : this.items.length > 0 + ? ready + : empty; + } + + get items() { + return this.itemData.data?.items ?? []; + } + + get subtotal() { + return this.items.reduce( + (acc, item) => acc + item.price * item.quantity, + 0 + ); + } + + get tax() { + return this.items.reduce((acc, item) => acc + item.taxAmount, 0); + } + + get shipping() { + return this.items.length > 0 ? 10 : 0; + } + + get total() { + return this.subtotal + this.tax + this.shipping; + } +} diff --git a/examples/shopping-cart/lwc/orderSummary/orderSummary.js-meta.xml b/examples/shopping-cart/lwc/orderSummary/orderSummary.js-meta.xml new file mode 100644 index 0000000..55d0827 --- /dev/null +++ b/examples/shopping-cart/lwc/orderSummary/orderSummary.js-meta.xml @@ -0,0 +1,11 @@ + + + 60.0 + Order Summary + true + Order Summary + + lightningCommunity__Default + lightningCommunity__Page + + diff --git a/examples/shopping-cart/lwc/orderSummary/states/empty.html b/examples/shopping-cart/lwc/orderSummary/states/empty.html new file mode 100644 index 0000000..cc340bc --- /dev/null +++ b/examples/shopping-cart/lwc/orderSummary/states/empty.html @@ -0,0 +1 @@ + diff --git a/examples/shopping-cart/lwc/orderSummary/states/loading.html b/examples/shopping-cart/lwc/orderSummary/states/loading.html new file mode 100644 index 0000000..cf7bd88 --- /dev/null +++ b/examples/shopping-cart/lwc/orderSummary/states/loading.html @@ -0,0 +1,6 @@ + + diff --git a/examples/shopping-cart/lwc/orderSummary/states/ready.html b/examples/shopping-cart/lwc/orderSummary/states/ready.html new file mode 100644 index 0000000..c900295 --- /dev/null +++ b/examples/shopping-cart/lwc/orderSummary/states/ready.html @@ -0,0 +1,54 @@ + diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.html b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.html new file mode 100644 index 0000000..cc340bc --- /dev/null +++ b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.html @@ -0,0 +1 @@ + diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js new file mode 100644 index 0000000..adbd133 --- /dev/null +++ b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js @@ -0,0 +1,78 @@ +import TwElement from "c/twElement"; +import { $computed } from "c/signals"; +import { + shoppingCart, + updateCart, + cartHistory, + undoCartChange +} from "c/demoSignals"; + +// States +import ready from "./states/ready.html"; +import loading from "./states/loading.html"; +import empty from "./states/empty.html"; + +export default class ShoppingCartDetails extends TwElement { + itemData = $computed(() => (this.itemData = shoppingCart.value)).value; + cartHistoryLength = $computed( + () => (this.cartHistoryLength = cartHistory.value.length) + ).value; + + connectedCallback() { + super.connectedCallback(); + } + + render() { + return this.itemData.loading + ? loading + : this.items.length > 0 + ? ready + : empty; + } + + get quantityOptions() { + return [ + { label: "1", value: 1 }, + { label: "2", value: 2 }, + { label: "3", value: 3 }, + { label: "4", value: 4 }, + { label: "5", value: 5 } + ]; + } + + get items() { + return ( + this.itemData.data.items?.map((item) => { + return { ...item, total: item.price * item.quantity }; + }) ?? [] + ); + } + + removeItem(event) { + event.preventDefault(); + const itemId = event.target.dataset.item; + updateCart({ + items: this.items.filter((item) => item.id !== itemId) + }); + } + + handleQuantityChange(event) { + const itemId = event.target.dataset.item; + const newQuantity = event.target.value; + const newItems = this.items.map((item) => { + if (item.id === itemId) { + return { ...item, quantity: newQuantity }; + } + return item; + }); + updateCart({ items: newItems }); + } + + get displayUndoPanel() { + return this.cartHistoryLength > 0; + } + + undo() { + undoCartChange(); + } +} diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js-meta.xml b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js-meta.xml new file mode 100644 index 0000000..5e20735 --- /dev/null +++ b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js-meta.xml @@ -0,0 +1,11 @@ + + + 60.0 + Shopping Cart Details + true + Shopping Cart Details + + lightningCommunity__Default + lightningCommunity__Page + + diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/states/empty.html b/examples/shopping-cart/lwc/shoppingCartDetails/states/empty.html new file mode 100644 index 0000000..c28488f --- /dev/null +++ b/examples/shopping-cart/lwc/shoppingCartDetails/states/empty.html @@ -0,0 +1,27 @@ + + diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/states/loading.html b/examples/shopping-cart/lwc/shoppingCartDetails/states/loading.html new file mode 100644 index 0000000..6309482 --- /dev/null +++ b/examples/shopping-cart/lwc/shoppingCartDetails/states/loading.html @@ -0,0 +1,6 @@ + + diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/states/ready.html b/examples/shopping-cart/lwc/shoppingCartDetails/states/ready.html new file mode 100644 index 0000000..98e77ff --- /dev/null +++ b/examples/shopping-cart/lwc/shoppingCartDetails/states/ready.html @@ -0,0 +1,140 @@ + diff --git a/examples/shopping-cart/lwc/stencil/stencil.css b/examples/shopping-cart/lwc/stencil/stencil.css new file mode 100644 index 0000000..c48a6f7 --- /dev/null +++ b/examples/shopping-cart/lwc/stencil/stencil.css @@ -0,0 +1,30 @@ +.container { + overflow: hidden; + border-radius: 0.25rem; +} + +.loading { + margin: 0; + padding: 0; + height: inherit; + overflow: hidden; + position: relative; + background-color: #e2e2e2; +} + +.loading::after { + display: block; + content: ''; + position: absolute; + width: 100%; + height: 100%; + transform: translateX(-100%); + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .2), transparent); + animation: loading 1.5s infinite; +} + +@keyframes loading { + 100% { + transform: translateX(100%); + } +} \ No newline at end of file diff --git a/examples/shopping-cart/lwc/stencil/stencil.html b/examples/shopping-cart/lwc/stencil/stencil.html new file mode 100644 index 0000000..c2bc2c9 --- /dev/null +++ b/examples/shopping-cart/lwc/stencil/stencil.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/examples/shopping-cart/lwc/stencil/stencil.js b/examples/shopping-cart/lwc/stencil/stencil.js new file mode 100644 index 0000000..51ef137 --- /dev/null +++ b/examples/shopping-cart/lwc/stencil/stencil.js @@ -0,0 +1,94 @@ +import { LightningElement, api } from 'lwc'; + +/** + * Stencil class for `c-stencil` component. + * @extends {LightningElement} + */ +export default class Stencil extends LightningElement { + /** + * The height to set on the stencil to rendered. + * @type {number} + * @access public + * @default 10 + */ + @api height = 10; + + /** + * The width to set on the stencil to render. + * @type {number} + * @access public + */ + @api width; + + /** + * Whether the stencil should render with a circular shape. + * @type {boolean} + * @access public + * @default false + */ + @api circle = false; + + /** + * The amount of stencil containers to create. + * @type {number} + * @access public + * @default 1 + */ + @api count = 1; + + /** + * The shade in which the stencil will be rendered. + * Possible values are `light`, `medium` or `dark`. + * @type {string} + * @access public + * @default medium + */ + @api weightVariant = 'medium'; + + get containerStyle() { + return `${this.containerHeight}; ${this.containerWidth}; ${this.containerRadius}`; + } + + get containerHeight() { + return `height: ${this.height}px`; + } + + get containerWidth() { + if (!this.width) { + return 'width: 100%'; + } + + return `width: ${this.width}px`; + } + + get containerRadius() { + if (!this.circle) { + return 'border-radius: 0.25rem'; + } + + return 'border-radius: 50%'; + } + + get items() { + let itemArray = []; + for (let i = 0; i < this.count; i++) { + itemArray.push(i.toString()); + } + + return itemArray; + } + + get loadingBackgroundColor() { + if (this.weightVariant === 'light') { + return 'background-color: #f3f2f2'; + } + + if (this.weightVariant === 'medium') { + return 'background-color: #e2e2e2'; + } + + if (this.weightVariant === 'dark') { + return 'background-color: #ccc'; + } + } +} \ No newline at end of file diff --git a/examples/shopping-cart/lwc/stencil/stencil.js-meta.xml b/examples/shopping-cart/lwc/stencil/stencil.js-meta.xml new file mode 100644 index 0000000..ec9b323 --- /dev/null +++ b/examples/shopping-cart/lwc/stencil/stencil.js-meta.xml @@ -0,0 +1,7 @@ + + + 60.0 + Stencil + false + Stencil + \ No newline at end of file diff --git a/examples/shopping-cart/lwc/twElement/twElement.html b/examples/shopping-cart/lwc/twElement/twElement.html new file mode 100644 index 0000000..c77e9ec --- /dev/null +++ b/examples/shopping-cart/lwc/twElement/twElement.html @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/examples/shopping-cart/lwc/twElement/twElement.js b/examples/shopping-cart/lwc/twElement/twElement.js new file mode 100644 index 0000000..36d2bf8 --- /dev/null +++ b/examples/shopping-cart/lwc/twElement/twElement.js @@ -0,0 +1,13 @@ +import {LightningElement} from 'lwc'; +import { loadStyle } from "lightning/platformResourceLoader"; + +import tw from '@salesforce/resourceUrl/tw'; + +export default class TwElement extends LightningElement { + connectedCallback() { + this.template.host.style.opacity = "0"; + loadStyle(this, `${tw}/css/main.css`).then(() => { + this.template.host.style.opacity = "1"; + }); + } +} \ No newline at end of file diff --git a/examples/shopping-cart/lwc/twElement/twElement.js-meta.xml b/examples/shopping-cart/lwc/twElement/twElement.js-meta.xml new file mode 100644 index 0000000..09c3d7b --- /dev/null +++ b/examples/shopping-cart/lwc/twElement/twElement.js-meta.xml @@ -0,0 +1,7 @@ + + + 60.0 + Tw Element + false + Tw Element + \ No newline at end of file diff --git a/examples/tw/input.css b/examples/tw/input.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/examples/tw/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/force-app/lwc/signals/core.js b/force-app/lwc/signals/core.js index 93a414b..87f5596 100644 --- a/force-app/lwc/signals/core.js +++ b/force-app/lwc/signals/core.js @@ -3,6 +3,25 @@ const context = []; function _getCurrentObserver() { return context[context.length - 1]; } +/** + * Creates a new effect that will be executed immediately and whenever + * any of the signals it reads from change. + * + * Avoid changing $signal values inside an effect, as it can lead to + * infinite loops. + * + * ```javascript + * import { $signal, $effect } from 'c/signals'; + * + * const count = $signal(0); + * + * $effect(() => { + * console.log(count.value); + * }); + * ``` + * + * @param fn The function to execute + */ function $effect(fn) { const execute = () => { context.push(execute); @@ -14,13 +33,53 @@ function $effect(fn) { }; execute(); } +/** + * Creates a new computed value that will be updated whenever the signals + * it reads from change. Returns a read-only signal that contains the + * computed value. + * + * ```javascript + * import { $signal, $computed } from 'c/signals'; + * + * const count = $signal(0); + * + * const double = $computed(() => count.value * 2); + * ``` + * + * @param fn The function that returns the computed value. + */ function $computed(fn) { - const computedSignal = $signal(fn()); + // The initial value is undefined, as it will be computed + // when the effect runs for the first time + const computedSignal = $signal(undefined); $effect(() => { computedSignal.value = fn(); }); return computedSignal.readOnly; } +/** + * Creates a new signal with the provided value. A signal is a reactive + * primitive that can be used to store and update values. Signals can be + * read and written to, and can be used to create computed values or + * can be read from within an effect. + * + * You can read the current value of a signal by accessing the `value` property. + * + * ```javascript + * import { $signal } from 'c/signals'; + * + * const count = $signal(0); + * + * // Read the current value, logs 0 + * console.log(count.value); + * + * // Update the value + * count.value = 1; + * ``` + * + * @param value The initial value of the signal + * @param options Options to configure the signal + */ function $signal( value, options = { @@ -37,7 +96,7 @@ function $signal( return _storageOption.get(); } function setter(newValue) { - if (newValue === _storageOption) { + if (newValue === _storageOption.get()) { return; } _storageOption.set(newValue); @@ -65,6 +124,54 @@ function $signal( delete returnValue.set; return returnValue; } +/** + * Creates a new resource that fetches data from an async source. The resource + * will automatically fetch the data when the component is mounted. + * + * It receives a function that returns a promise, which will be called to fetch + * the data. Optionally, you can provide a source object or function that will + * be used as the parameters for the fetch function. + * + * If a function that contain $computed values is provided as the source, the + * resource will automatically refetch the data when the computed value changes. + * + * `$resource` returns an object with 2 properties: + * - `data`: a signal that contains the current state of the resource. It has + * the following shape: + * ```javascript + * { + * data: T | null; + * loading: boolean; + * error: unknown | null; + * } + * ``` + * + * - `refetch`: a function that can be called to force refetch the data. + * + * ```javascript + * import { $signal, $resource } from 'c/signals'; + * import getAccounts from '@salesforce/apex/AccountController.getAccounts'; + * + * const accountId = $signal('00B5e00000Dv9ZCEAZ'); + * + * // If the account Id value is changed, the resource will automatically refetch the data + * const { data: accounts, refetch } = $resource(getAccounts, () => ({ recordId: accountId.value })); + * + * export { accounts, refetch }; + * + * // Usage from a component + * import { LightningElement } from 'lwc'; + * import { $computed } from 'c/signals'; + * import { accounts, refetch } from 'c/myResource'; + * + * export default class MyComponent extends LightningElement { + * accounts = $computed(() => this.accounts = accounts.value).value; + * } + * + * @param fn The function that will be called to fetch the data. Usually an Apex method but can be any async function. + * @param source The source object or function that will be used as the parameters for the fetch function + * @param options The options to configure the resource. Allows you to provide an initial value for the resource. + */ function $resource(fn, source, options) { function loadingState(data) { return { @@ -77,6 +184,8 @@ function $resource(fn, source, options) { let _value = options?.initialValue ?? null; let _previousParams; const _signal = $signal(loadingState(_value)); + // Optimistic updates are enabled by default + const optimisticMutate = options?.optimisticMutate ?? true; const execute = async () => { _signal.value = loadingState(_value); const derivedSource = source instanceof Function ? source() : source; @@ -105,8 +214,31 @@ function $resource(fn, source, options) { } }; $effect(execute); + /** + * Callback function that updates the value of the resource. + * @param value The value we want to set the resource to. + * @param error An optional error object. + */ + function mutatorCallback(value, error) { + _value = value; + _signal.value = { + data: value, + loading: false, + error: error ?? null + }; + } return { data: _signal.readOnly, + mutate: (newValue) => { + const previousValue = _value; + if (optimisticMutate) { + // If optimistic updates are enabled, update the value immediately + mutatorCallback(newValue); + } + if (options?.onMutate) { + options.onMutate(newValue, previousValue, mutatorCallback); + } + }, refetch: async () => { _isInitialLoad = true; await execute(); diff --git a/force-app/lwc/signals/signals.js-meta.xml b/force-app/lwc/signals/signals.js-meta.xml index 0488e66..48a3735 100644 --- a/force-app/lwc/signals/signals.js-meta.xml +++ b/force-app/lwc/signals/signals.js-meta.xml @@ -4,4 +4,4 @@ Signals true Signals - + \ No newline at end of file diff --git a/force-app/lwc/signals/use.js b/force-app/lwc/signals/use.js index 1b874e4..3ce7421 100644 --- a/force-app/lwc/signals/use.js +++ b/force-app/lwc/signals/use.js @@ -1,52 +1,52 @@ export function createStorage(get, set) { - return { get, set }; + return { get, set }; } export function useInMemoryStorage(value) { - let _value = value; - function getter() { - return _value; - } - function setter(newValue) { - _value = newValue; - } - return createStorage(getter, setter); -} -function useLocalStorageCreator(key, value) { - function getter() { - const item = localStorage.getItem(key); - if (item) { - return JSON.parse(item); + let _value = value; + function getter() { + return _value; } - return value; - } - function setter(newValue) { - localStorage.setItem(key, JSON.stringify(newValue)); - } - // Set initial value if not set - if (!localStorage.getItem(key)) { - localStorage.setItem(key, JSON.stringify(value)); - } - return createStorage(getter, setter); -} -export function useLocalStorage(key) { - return function (value) { - return useLocalStorageCreator(key, value); - }; + function setter(newValue) { + _value = newValue; + } + return createStorage(getter, setter); } -export function useCookies(key, expires) { - return function (value) { +function useLocalStorageCreator(key, value) { function getter() { - const cookie = document.cookie - .split("; ") - .find((row) => row.startsWith(key)); - if (cookie) { - return JSON.parse(cookie.split("=")[1]); - } - return value; + const item = localStorage.getItem(key); + if (item) { + return JSON.parse(item); + } + return value; } function setter(newValue) { - document.cookie = `${key}=${JSON.stringify(newValue)}; expires=${expires?.toUTCString()}`; + localStorage.setItem(key, JSON.stringify(newValue)); + } + // Set initial value if not set + if (!localStorage.getItem(key)) { + localStorage.setItem(key, JSON.stringify(value)); } return createStorage(getter, setter); - }; +} +export function useLocalStorage(key) { + return function (value) { + return useLocalStorageCreator(key, value); + }; +} +export function useCookies(key, expires) { + return function (value) { + function getter() { + const cookie = document.cookie + .split("; ") + .find((row) => row.startsWith(key)); + if (cookie) { + return JSON.parse(cookie.split("=")[1]); + } + return value; + } + function setter(newValue) { + document.cookie = `${key}=${JSON.stringify(newValue)}; expires=${expires?.toUTCString()}`; + } + return createStorage(getter, setter); + }; } diff --git a/package-lock.json b/package-lock.json index a4a8ec7..3ed6e11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@salesforce/eslint-config-lwc": "^3.2.3", "@salesforce/eslint-plugin-aura": "^2.0.0", "@salesforce/eslint-plugin-lightning": "^1.0.0", + "@tailwindcss/forms": "^0.5.7", "@types/jest": "^29.5.12", "eslint": "^8.57.0", "eslint-plugin-import": "^2.25.4", @@ -26,12 +27,25 @@ "lint-staged": "^15.1.0", "prettier": "^3.1.0", "prettier-plugin-apex": "^2.0.1", + "tailwindcss": "^3.4.3", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.4.5", "typescript-eslint": "^7.9.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -763,6 +777,102 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1641,6 +1751,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@prettier/plugin-xml": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@prettier/plugin-xml/-/plugin-xml-3.4.1.tgz", @@ -1828,6 +1948,18 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", + "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==", + "dev": true, + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -2415,6 +2547,12 @@ "node": ">=4" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2787,6 +2925,18 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -2904,6 +3054,15 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001620", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", @@ -2956,6 +3115,42 @@ "regexp-to-ast": "0.5.0" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -3282,6 +3477,18 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -3475,6 +3682,12 @@ "node": ">=8" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3505,6 +3718,12 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3530,6 +3749,12 @@ "node": ">=12" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/electron-to-chromium": { "version": "1.4.773", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.773.tgz", @@ -4627,6 +4852,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -5189,6 +5442,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -5557,6 +5822,24 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", + "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -7140,6 +7423,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/joi": { "version": "17.13.1", "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", @@ -7752,6 +8044,15 @@ "node": ">=6" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { "version": "9.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", @@ -7776,12 +8077,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -7827,6 +8166,24 @@ "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -8067,6 +8424,31 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8106,6 +8488,15 @@ "node": ">=0.10" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -8188,17 +8579,154 @@ "node": ">= 0.4" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.2.5", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, @@ -8340,6 +8868,27 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/regexp-to-ast": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", @@ -8711,6 +9260,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -8787,6 +9345,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -8875,6 +9463,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -8905,6 +9506,59 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -8935,6 +9589,58 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tailwindcss": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -8977,6 +9683,27 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -9043,6 +9770,12 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/ts-jest": { "version": "29.1.2", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", @@ -9634,6 +10367,12 @@ "requires-port": "^1.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -9813,6 +10552,86 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", diff --git a/package.json b/package.json index 967752c..127ca09 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,13 @@ "test:coverage": "jest --coverage", "create:scratch": "sf org create scratch --alias lwc-signals --definition-file config/project-scratch-def.json --set-default", "dev:start": "npm run create:scratch && sf project deploy start", - "lint": "eslint **/{aura,lwc}/**/*.js", + "lint": "eslint ./{examples,force-app,src}/**/{lwc}/*.{js,ts} --no-error-on-unmatched-pattern", "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", "prettier:verify": "prettier --check \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", "postinstall": "husky install", - "precommit": "lint-staged" + "precommit": "lint-staged", + "tw:build": "npx tailwindcss -i ./examples/tw/input.css -o ./examples/main/default/staticresources/tw/css/main.css --minify", + "tw:build:watch": "npx tailwindcss -i ./examples/tw/input.css -o ./examples/main/default/staticresources/tw/css/main.css --minify --watch" }, "devDependencies": { "@eslint/js": "^9.2.0", @@ -31,17 +33,18 @@ "jest-environment-jsdom": "^29.7.0", "lint-staged": "^15.1.0", "prettier": "^3.1.0", - "prettier-plugin-apex": "^2.0.1", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.4.5", - "typescript-eslint": "^7.9.0" + "typescript-eslint": "^7.9.0", + "tailwindcss": "^3.4.3", + "@tailwindcss/forms": "^0.5.7" }, "lint-staged": { - "**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [ + "**/*.{cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [ "prettier --write" ], - "**/{aura,lwc}/**/*.js": [ + "**/{lwc}/**/*.js": [ "eslint" ] } diff --git a/sfdx-project.json b/sfdx-project.json index 7959923..c1780ba 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -14,10 +14,6 @@ } ], "name": "lwc-signals", - "namespace": "signal", "sfdcLoginUrl": "https://login.salesforce.com", - "sourceApiVersion": "60.0", - "packageAliases": { - "LWC Signals": "0HoRb000000004rKAA" - } + "sourceApiVersion": "60.0" } diff --git a/src/lwc/signals/__tests__/signals.test.ts b/src/lwc/signals/__tests__/signals.test.ts index f35d714..467b85e 100644 --- a/src/lwc/signals/__tests__/signals.test.ts +++ b/src/lwc/signals/__tests__/signals.test.ts @@ -22,6 +22,29 @@ describe("signals", () => { expect(computed.value).toBe(2); }); + test("does not recompute when the same value is set", () => { + const signal = $signal(0); + + let timesComputed = 0; + const computed = $computed(() => { + timesComputed++; + return signal.value * 2; + }); + + expect(computed.value).toBe(0); + expect(timesComputed).toBe(1); + + signal.value = 1; + + expect(computed.value).toBe(2); + expect(timesComputed).toBe(2); + + signal.value = 1; + + expect(computed.value).toBe(2); + expect(timesComputed).toBe(2); + }); + test("can derive a computed value from another computed value", () => { const signal = $signal(0); const computed = $computed(() => signal.value * 2); @@ -157,13 +180,12 @@ describe("signals", () => { }); }); - test("can force a refetch of a resource", async () => { - let counter = 0; + test("can mutate a resource", async () => { const asyncFunction = async () => { - return counter++; + return "done"; }; - const { data: resource, refetch } = $resource(asyncFunction); + const { data: resource, mutate } = $resource(asyncFunction); expect(resource.value).toEqual({ data: null, @@ -174,104 +196,244 @@ describe("signals", () => { await new Promise(process.nextTick); expect(resource.value).toEqual({ - data: 0, + data: "done", loading: false, error: null }); - refetch(); + mutate("mutated"); expect(resource.value).toEqual({ - data: 0, - loading: true, + data: "mutated", + loading: false, error: null }); + }); + + test("does not mutate a resource if optimistic updating is not turned on and no onMutate is provided", async () => { + const asyncFunction = async () => { + return "done"; + }; + + const { data: resource, mutate } = $resource(asyncFunction, undefined, { + optimisticMutate: false + }); await new Promise(process.nextTick); expect(resource.value).toEqual({ - data: 1, + data: "done", + loading: false, + error: null + }); + + mutate("mutated"); + + expect(resource.value).toEqual({ + data: "done", loading: false, error: null }); }); - test("can create custom storages", () => { - const useUndo = (value: T) => { - const _valueStack: T[] = []; + test("can react to a mutation", async () => { + const asyncFunction = async () => { + return "done"; + }; - // add the initial value to the stack - _valueStack.push(value); + let hasReacted = false; + const reactionFunction = () => { + hasReacted = true; + }; - function undo() { - _valueStack.pop(); - } + const { mutate } = $resource(asyncFunction, undefined, { + onMutate: reactionFunction + }); - const customStorage = createStorage( - () => { - // Get value at the top of the stack - return _valueStack[_valueStack.length - 1]; - }, - (newValue) => { - _valueStack.push(newValue); - }, - ); - - return { - ...customStorage, - undo - }; + await new Promise(process.nextTick); + + mutate("mutated"); + + await new Promise(process.nextTick); + + expect(hasReacted).toBe(true); + }); + + test("can mutate a resource and change the value on success", async () => { + const asyncFunction = async () => { + return "done"; }; - const signal = $signal(0, { - storage: useUndo - }) as unknown as Signal & { undo: () => void }; + const asyncReaction = async (newValue: string, __: string | null, mutate: (value: string | null, error?: unknown) => void) => { + mutate(`${newValue} - post async success`); + }; - expect(signal.value).toBe(0); + const { data: resource, mutate } = $resource(asyncFunction, undefined, { + onMutate: asyncReaction + }); - signal.value = 1; - expect(signal.value).toBe(1); + await new Promise(process.nextTick); - signal.value = 2; - expect(signal.value).toBe(2); + expect(resource.value).toEqual({ + data: "done", + loading: false, + error: null + }); - signal.undo(); - expect(signal.value).toBe(1); + mutate("mutated"); - signal.undo(); - expect(signal.value).toBe(0); + expect(resource.value).toEqual({ + data: "mutated - post async success", + loading: false, + error: null + }); }); - }); - describe("storing values in local storage", () => { - test("should have a default value", () => { - const signal = $signal(0, { - storage: useLocalStorage("test") + test('the onMutate function can set an error', async () => { + const asyncFunction = async () => { + return 'done'; + }; + + const asyncReaction = async (newValue: string, _: string | null, mutate: (value: string | null, error?: unknown) => void) => { + mutate(null, 'An error occurred'); + }; + + const { data: resource, mutate } = $resource(asyncFunction, undefined, { + onMutate: asyncReaction + }); + + await new Promise(process.nextTick); + + expect(resource.value).toEqual({ + data: 'done', + loading: false, + error: null + }); + + mutate('mutated'); + + expect(resource.value).toEqual({ + data: null, + loading: false, + error: 'An error occurred' }); - expect(signal.value).toBe(0); }); + }); - test("should update the value", () => { - const signal = $signal(0); - signal.value = 1; - expect(signal.value).toBe(1); + test("can force a refetch of a resource", async () => { + let counter = 0; + const asyncFunction = async () => { + return counter++; + }; + + const { data: resource, refetch } = $resource(asyncFunction); + + expect(resource.value).toEqual({ + data: null, + loading: true, + error: null + }); + + await new Promise(process.nextTick); + + expect(resource.value).toEqual({ + data: 0, + loading: false, + error: null + }); + + refetch(); + + expect(resource.value).toEqual({ + data: 0, + loading: true, + error: null + }); + + await new Promise(process.nextTick); + + expect(resource.value).toEqual({ + data: 1, + loading: false, + error: null }); }); - describe("storing values in cookies", () => { - test("should have a default value", () => { - const signal = $signal(0, { - storage: useCookies("test") - }); - expect(signal.value).toBe(0); + test("can create custom storages", () => { + const useUndo = (value: T) => { + const _valueStack: T[] = []; + + // add the initial value to the stack + _valueStack.push(value); + + function undo() { + _valueStack.pop(); + } + + const customStorage = createStorage( + () => { + // Get value at the top of the stack + return _valueStack[_valueStack.length - 1]; + }, + (newValue) => { + _valueStack.push(newValue); + } + ); + + return { + ...customStorage, + undo + }; + }; + + const signal = $signal(0, { + storage: useUndo + }) as unknown as Signal & { undo: () => void }; + + expect(signal.value).toBe(0); + + signal.value = 1; + expect(signal.value).toBe(1); + + signal.value = 2; + expect(signal.value).toBe(2); + + signal.undo(); + expect(signal.value).toBe(1); + + signal.undo(); + expect(signal.value).toBe(0); + }); +}); + +describe("storing values in local storage", () => { + test("should have a default value", () => { + const signal = $signal(0, { + storage: useLocalStorage("test") }); + expect(signal.value).toBe(0); + }); - test("should update the value", () => { - const signal = $signal(0, { - storage: useCookies("test") - }); - signal.value = 1; - expect(signal.value).toBe(1); + test("should update the value", () => { + const signal = $signal(0); + signal.value = 1; + expect(signal.value).toBe(1); + }); +}); + +describe("storing values in cookies", () => { + test("should have a default value", () => { + const signal = $signal(0, { + storage: useCookies("test") + }); + expect(signal.value).toBe(0); + }); + + test("should update the value", () => { + const signal = $signal(0, { + storage: useCookies("test") }); + signal.value = 1; + expect(signal.value).toBe(1); }); }); diff --git a/src/lwc/signals/core.ts b/src/lwc/signals/core.ts index 7b3dabc..a20f346 100644 --- a/src/lwc/signals/core.ts +++ b/src/lwc/signals/core.ts @@ -16,6 +16,25 @@ function _getCurrentObserver(): VoidFunction | undefined { return context[context.length - 1]; } +/** + * Creates a new effect that will be executed immediately and whenever + * any of the signals it reads from change. + * + * Avoid changing $signal values inside an effect, as it can lead to + * infinite loops. + * + * ```javascript + * import { $signal, $effect } from 'c/signals'; + * + * const count = $signal(0); + * + * $effect(() => { + * console.log(count.value); + * }); + * ``` + * + * @param fn The function to execute + */ function $effect(fn: VoidFunction): void { const execute = () => { context.push(execute); @@ -31,14 +50,31 @@ function $effect(fn: VoidFunction): void { type ComputedFunction = () => T; +/** + * Creates a new computed value that will be updated whenever the signals + * it reads from change. Returns a read-only signal that contains the + * computed value. + * + * ```javascript + * import { $signal, $computed } from 'c/signals'; + * + * const count = $signal(0); + * + * const double = $computed(() => count.value * 2); + * ``` + * + * @param fn The function that returns the computed value. + */ function $computed(fn: ComputedFunction): ReadOnlySignal { - const computedSignal: Signal = $signal(fn()); + // The initial value is undefined, as it will be computed + // when the effect runs for the first time + const computedSignal: Signal = $signal(undefined); $effect(() => { computedSignal.value = fn(); }); - return computedSignal.readOnly; + return computedSignal.readOnly as ReadOnlySignal; } type StorageFn = (value: T) => State & { [key: string]: unknown }; @@ -47,9 +83,32 @@ type SignalOptions = { storage: StorageFn }; +/** + * Creates a new signal with the provided value. A signal is a reactive + * primitive that can be used to store and update values. Signals can be + * read and written to, and can be used to create computed values or + * can be read from within an effect. + * + * You can read the current value of a signal by accessing the `value` property. + * + * ```javascript + * import { $signal } from 'c/signals'; + * + * const count = $signal(0); + * + * // Read the current value, logs 0 + * console.log(count.value); + * + * // Update the value + * count.value = 1; + * ``` + * + * @param value The initial value of the signal + * @param options Options to configure the signal + */ function $signal(value: T, options: SignalOptions = { storage: useInMemoryStorage -}): Signal & Omit>, 'get' | 'set'> { +}): Signal & Omit>, "get" | "set"> { const _storageOption: State = options.storage(value); const subscribers: Set = new Set(); @@ -62,7 +121,7 @@ function $signal(value: T, options: SignalOptions = { } function setter(newValue: T) { - if (newValue === _storageOption) { + if (newValue === _storageOption.get()) { return; } _storageOption.set(newValue); @@ -71,7 +130,7 @@ function $signal(value: T, options: SignalOptions = { } } - const returnValue: Signal & Omit>, 'get' | 'set'> = { + const returnValue: Signal & Omit>, "get" | "set"> = { ..._storageOption, get value() { return getter(); @@ -104,15 +163,71 @@ type AsyncData = { type ResourceResponse = { data: ReadOnlySignal>; + mutate: (newValue: T) => void; refetch: () => void; }; type UnknownArgsMap = { [key: string]: unknown }; +type MutatorCallback = (value: T | null, error?: unknown) => void; + +type OnMutate = (newValue: T, oldValue: T | null, mutate: MutatorCallback) => Promise | void; + type ResourceOptions = { initialValue?: T; + optimisticMutate?: boolean; + onMutate?: OnMutate; + storage?: StorageFn; }; +/** + * Creates a new resource that fetches data from an async source. The resource + * will automatically fetch the data when the component is mounted. + * + * It receives a function that returns a promise, which will be called to fetch + * the data. Optionally, you can provide a source object or function that will + * be used as the parameters for the fetch function. + * + * If a function that contain $computed values is provided as the source, the + * resource will automatically refetch the data when the computed value changes. + * + * `$resource` returns an object with 2 properties: + * - `data`: a signal that contains the current state of the resource. It has + * the following shape: + * ```javascript + * { + * data: T | null; + * loading: boolean; + * error: unknown | null; + * } + * ``` + * + * - `refetch`: a function that can be called to force refetch the data. + * + * ```javascript + * import { $signal, $resource } from 'c/signals'; + * import getAccounts from '@salesforce/apex/AccountController.getAccounts'; + * + * const accountId = $signal('00B5e00000Dv9ZCEAZ'); + * + * // If the account Id value is changed, the resource will automatically refetch the data + * const { data: accounts, refetch } = $resource(getAccounts, () => ({ recordId: accountId.value })); + * + * export { accounts, refetch }; + * + * // Usage from a component + * import { LightningElement } from 'lwc'; + * import { $computed } from 'c/signals'; + * import { accounts, refetch } from 'c/myResource'; + * + * export default class MyComponent extends LightningElement { + * accounts = $computed(() => this.accounts = accounts.value).value; + * } + * + * @param fn The function that will be called to fetch the data. Usually an Apex method but can be any async function. + * @param source The source object or function that will be used as the parameters for the fetch function + * @param options The options to configure the resource. Allows you to provide an initial value for the resource. + */ function $resource( fn: (params?: { [key: string]: unknown }) => Promise, source?: UnknownArgsMap | (() => UnknownArgsMap), @@ -130,6 +245,8 @@ function $resource( let _value: T | null = options?.initialValue ?? null; let _previousParams: UnknownArgsMap | undefined; const _signal = $signal>(loadingState(_value)); + // Optimistic updates are enabled by default + const optimisticMutate = options?.optimisticMutate ?? true; const execute = async () => { _signal.value = loadingState(_value); @@ -165,8 +282,33 @@ function $resource( $effect(execute); + /** + * Callback function that updates the value of the resource. + * @param value The value we want to set the resource to. + * @param error An optional error object. + */ + function mutatorCallback(value: T | null, error?: unknown): void { + _value = value; + _signal.value = { + data: value, + loading: false, + error: error ?? null + }; + } + return { data: _signal.readOnly, + mutate: (newValue: T) => { + const previousValue = _value; + if (optimisticMutate) { + // If optimistic updates are enabled, update the value immediately + mutatorCallback(newValue); + } + + if (options?.onMutate) { + options.onMutate(newValue, previousValue, mutatorCallback); + } + }, refetch: async () => { _isInitialLoad = true; await execute(); diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..cfd9407 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,9 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./examples/**/*.{html,js}"], + important: true, + theme: { + extend: {} + }, + plugins: [] +};