diff --git a/api/7.2.0/AccountLockoutOptions.html b/api/7.2.0/AccountLockoutOptions.html new file mode 100644 index 0000000000..e9398db97f --- /dev/null +++ b/api/7.2.0/AccountLockoutOptions.html @@ -0,0 +1,3 @@ +Interface: AccountLockoutOptions
On this page

AccountLockoutOptions

Properties
NameTypeDescription
durationNumber

Set the duration in minutes that a locked-out account remains locked out before automatically becoming unlocked.

Valid values are greater than 0 and less than 100000.

thresholdNumber

Set the number of failed sign-in attempts that will cause a user account to be locked. If the account is locked. The account will unlock after the duration set in the duration option has passed and no further login attempts have been made.

Valid values are greater than 0 and less than 1000.

unlockOnPasswordResetBoolean

Set to true if the account should be unlocked after a successful password reset.

Default is false.
Requires options duration and threshold to be set.

\ No newline at end of file diff --git a/api/7.2.0/AuthAdapter.html b/api/7.2.0/AuthAdapter.html new file mode 100644 index 0000000000..be8ee140ae --- /dev/null +++ b/api/7.2.0/AuthAdapter.html @@ -0,0 +1,3 @@ +Interface: AuthAdapter
On this page

AuthAdapter

Properties
NameTypeDescription
enabledBoolean

Is true if the auth adapter is enabled, false otherwise.

Methods

afterFind(authData, options) → {Promise.<Object>}

Triggered when auth data is fetched

Parameters:
NameTypeDescription
authDataObject

authData

optionsObject

additional adapter options

Returns:

Any overrides required to authData

Type: 
Promise.<Object>

challenge(challengeData, authData, options, request) → {Promise.<Object>}

Triggered in pre authentication process if needed (like webauthn, SMS OTP)

Parameters:
NameTypeDescription
challengeDataObject

Data provided by the client

authDataObject | undefined

Auth data provided by the client, can be used for validation

optionsObject

additional adapter options

requestParse.Cloud.TriggerRequest
Returns:

A promise that resolves, resolved value will be added to challenge response under challenge key

Type: 
Promise.<Object>

validateAppId(appIds, authData, options, request) → {Promise.<(undefined|void)>|void|undefined}

Parameters:
NameTypeDescription
appIds

The specified app IDs in the configuration

authDataObject

The client provided authData

optionsObject

additional adapter options

requestParse.Cloud.TriggerRequest
Returns:

resolves or returns if the applicationId is valid

Type: 
Promise.<(undefined|void)> | void | undefined

validateAuthData(authData, request, options) → {Promise.<(ParseAuthResponse|void|undefined)>}

Legacy usage, if provided it will be triggered when authData related to this provider is touched (signup/update/login) otherwise you should implement validateSetup, validateLogin and validateUpdate

Parameters:
NameTypeDescription
authDataObject

The client provided authData

requestParse.Cloud.TriggerRequest
optionsObject

additional adapter options

Returns:
Type: 
Promise.<(ParseAuthResponse|void|undefined)>

validateLogin(authData, request, options) → {Promise.<(ParseAuthResponse|void|undefined)>}

Triggered when user provide authData related to this provider The user is not logged in and has already set this provider before

Parameters:
NameTypeDescription
authDataObject

The client provided authData

requestParse.Cloud.TriggerRequest
optionsObject

additional adapter options

Returns:
Type: 
Promise.<(ParseAuthResponse|void|undefined)>

validateOptions(options)

Triggered when the adapter is first attached to Parse Server

Parameters:
NameTypeDescription
optionsObject

Adapter Options

validateSetUp(authData, request, options) → {Promise.<(ParseAuthResponse|void|undefined)>}

Triggered when user provide for the first time this auth provider could be a register or the user adding a new auth service

Parameters:
NameTypeDescription
authDataObject

The client provided authData

requestParse.Cloud.TriggerRequest
optionsObject

additional adapter options

Returns:
Type: 
Promise.<(ParseAuthResponse|void|undefined)>

validateUpdate(authData, options, request) → {Promise.<(ParseAuthResponse|void|undefined)>}

Triggered when user provide authData related to this provider the user is logged in and has already set this provider before

Parameters:
NameTypeDescription
authDataObject

The client provided authData

optionsObject

additional adapter options

requestParse.Cloud.TriggerRequest
Returns:
Type: 
Promise.<(ParseAuthResponse|void|undefined)>
\ No newline at end of file diff --git a/api/7.2.0/CustomPagesOptions.html b/api/7.2.0/CustomPagesOptions.html new file mode 100644 index 0000000000..ba73ba11c3 --- /dev/null +++ b/api/7.2.0/CustomPagesOptions.html @@ -0,0 +1,3 @@ +Interface: CustomPagesOptions
On this page

CustomPagesOptions

Properties
NameTypeDescription
choosePasswordString

choose password page path

expiredVerificationLinkString

expired verification link page path

invalidLinkString

invalid link page path

invalidPasswordResetLinkString

invalid password reset link page path

invalidVerificationLinkString

invalid verification link page path

linkSendFailString

verification link send fail page path

linkSendSuccessString

verification link send success page path

parseFrameURLString

for masking user-facing pages

passwordResetSuccessString

password reset success page path

verifyEmailSuccessString

verify email success page path

\ No newline at end of file diff --git a/api/7.2.0/DatabaseOptions.html b/api/7.2.0/DatabaseOptions.html new file mode 100644 index 0000000000..beba5b176d --- /dev/null +++ b/api/7.2.0/DatabaseOptions.html @@ -0,0 +1,3 @@ +Interface: DatabaseOptions
On this page

DatabaseOptions

Properties
NameTypeDescription
enableSchemaHooksBoolean

Enables database real-time hooks to update single schema cache. Set to true if using multiple Parse Servers instances connected to the same database. Failing to do so will cause a schema change to not propagate to all instances and re-syncing will only happen when the instances restart. To use this feature with MongoDB, a replica set cluster with change stream support is required.

schemaCacheTtlNumber

The duration in seconds after which the schema cache expires and will be refetched from the database. Use this option if using multiple Parse Servers instances connected to the same database. A low duration will cause the schema cache to be updated too often, causing unnecessary database reads. A high duration will cause the schema to be updated too rarely, increasing the time required until schema changes propagate to all server instances. This feature can be used as an alternative or in conjunction with the option enableSchemaHooks. Default is infinite which means the schema cache never expires.

\ No newline at end of file diff --git a/api/7.2.0/FileUploadOptions.html b/api/7.2.0/FileUploadOptions.html new file mode 100644 index 0000000000..7e7706a38b --- /dev/null +++ b/api/7.2.0/FileUploadOptions.html @@ -0,0 +1,3 @@ +Interface: FileUploadOptions
On this page

FileUploadOptions

Properties
NameTypeDescription
enableForAnonymousUserBoolean

Is true if file upload should be allowed for anonymous users.

enableForAuthenticatedUserBoolean

Is true if file upload should be allowed for authenticated users.

enableForPublicBoolean

Is true if file upload should be allowed for anyone, regardless of user authentication.

fileExtensionsArray.<String>

Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.

It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.

Defaults to ^(?!(h|H)(t|T)(m|M)(l|L)?$) which allows any file extension except HTML files.

\ No newline at end of file diff --git a/api/7.2.0/IdempotencyOptions.html b/api/7.2.0/IdempotencyOptions.html new file mode 100644 index 0000000000..3dbe875770 --- /dev/null +++ b/api/7.2.0/IdempotencyOptions.html @@ -0,0 +1,3 @@ +Interface: IdempotencyOptions
On this page

IdempotencyOptions

Properties
NameTypeDescription
pathsArray.<String>

An array of paths for which the feature should be enabled. The mount path must not be included, for example instead of /parse/functions/myFunction specifiy functions/myFunction. The entries are interpreted as regular expression, for example functions/.* matches all functions, jobs/.* matches all jobs, classes/.* matches all classes, .* matches all paths.

ttlNumber

The duration in seconds after which a request record is discarded from the database, defaults to 300s.

\ No newline at end of file diff --git a/api/7.2.0/LiveQueryOptions.html b/api/7.2.0/LiveQueryOptions.html new file mode 100644 index 0000000000..42c979c95c --- /dev/null +++ b/api/7.2.0/LiveQueryOptions.html @@ -0,0 +1,3 @@ +Interface: LiveQueryOptions
On this page

LiveQueryOptions

Properties
NameTypeDescription
classNamesArray.<String>

parse-server's LiveQuery classNames

pubSubAdapterAdapter.<PubSubAdapter>

LiveQuery pubsub adapter

redisOptionsAny

parse-server's LiveQuery redisOptions

redisURLString

parse-server's LiveQuery redisURL

wssAdapterAdapter.<WSSAdapter>

Adapter module for the WebSocketServer

\ No newline at end of file diff --git a/api/7.2.0/LiveQueryServerOptions.html b/api/7.2.0/LiveQueryServerOptions.html new file mode 100644 index 0000000000..498343cb1b --- /dev/null +++ b/api/7.2.0/LiveQueryServerOptions.html @@ -0,0 +1,3 @@ +Interface: LiveQueryServerOptions
On this page

LiveQueryServerOptions

Properties
NameTypeDescription
appIdString

This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId.

cacheTimeoutNumber

Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 5 * 1000 ms (5 seconds).

keyPairsAny

A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details.

logLevelString

This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE, defaults to INFO.

masterKeyString

This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey.

portNumber

The port to run the LiveQuery server, defaults to 1337.

pubSubAdapterAdapter.<PubSubAdapter>

LiveQuery pubsub adapter

redisOptionsAny

parse-server's LiveQuery redisOptions

redisURLString

parse-server's LiveQuery redisURL

serverURLString

This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL.

websocketTimeoutNumber

Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients, defaults to 10 * 1000 ms (10 s).

wssAdapterAdapter.<WSSAdapter>

Adapter module for the WebSocketServer

\ No newline at end of file diff --git a/api/7.2.0/LogLevels.html b/api/7.2.0/LogLevels.html new file mode 100644 index 0000000000..7084f67776 --- /dev/null +++ b/api/7.2.0/LogLevels.html @@ -0,0 +1,3 @@ +Interface: LogLevels
On this page

LogLevels

Properties
NameTypeDescription
cloudFunctionErrorString

Log level used by the Cloud Code Functions on error. Default is error.

cloudFunctionSuccessString

Log level used by the Cloud Code Functions on success. Default is info.

triggerAfterString

Log level used by the Cloud Code Triggers afterSave, afterDelete, afterFind, afterLogout. Default is info.

triggerBeforeErrorString

Log level used by the Cloud Code Triggers beforeSave, beforeDelete, beforeFind, beforeLogin on error. Default is error.

triggerBeforeSuccessString

Log level used by the Cloud Code Triggers beforeSave, beforeDelete, beforeFind, beforeLogin on success. Default is info.

\ No newline at end of file diff --git a/api/7.2.0/PagesCustomUrlsOptions.html b/api/7.2.0/PagesCustomUrlsOptions.html new file mode 100644 index 0000000000..ea4857f779 --- /dev/null +++ b/api/7.2.0/PagesCustomUrlsOptions.html @@ -0,0 +1,3 @@ +Interface: PagesCustomUrlsOptions
On this page

PagesCustomUrlsOptions

Properties
NameTypeDescription
emailVerificationLinkExpiredString

The URL to the custom page for email verification -> link expired.

emailVerificationLinkInvalidString

The URL to the custom page for email verification -> link invalid.

emailVerificationSendFailString

The URL to the custom page for email verification -> link send fail.

emailVerificationSendSuccessString

The URL to the custom page for email verification -> resend link -> success.

emailVerificationSuccessString

The URL to the custom page for email verification -> success.

passwordResetString

The URL to the custom page for password reset.

passwordResetLinkInvalidString

The URL to the custom page for password reset -> link invalid.

passwordResetSuccessString

The URL to the custom page for password reset -> success.

\ No newline at end of file diff --git a/api/7.2.0/PagesOptions.html b/api/7.2.0/PagesOptions.html new file mode 100644 index 0000000000..47e27d08b4 --- /dev/null +++ b/api/7.2.0/PagesOptions.html @@ -0,0 +1,3 @@ +Interface: PagesOptions
On this page

PagesOptions

Properties
NameTypeDescription
customRoutesArray.<PagesRoute>

The custom routes.

customUrlsPagesCustomUrlsOptions

The URLs to the custom pages.

enableLocalizationBoolean

Is true if pages should be localized; this has no effect on custom page redirects.

enableRouterBoolean

Is true if the pages router should be enabled; this is required for any of the pages options to take effect. Caution, this is an experimental feature that may not be appropriate for production.

forceRedirectBoolean

Is true if responses should always be redirects and never content, false if the response type should depend on the request type (GET request -> content response; POST request -> redirect response).

localizationFallbackLocaleString

The fallback locale for localization if no matching translation is provided for the given locale. This is only relevant when providing translation resources via JSON file.

localizationJsonPathString

The path to the JSON file for localization; the translations will be used to fill template placeholders according to the locale.

pagesEndpointString

The API endpoint for the pages. Default is 'apps'.

pagesPathString

The path to the pages directory; this also defines where the static endpoint '/apps' points to. Default is the './public/' directory.

placeholdersObject

The placeholder keys and values which will be filled in pages; this can be a simple object or a callback function.

\ No newline at end of file diff --git a/api/7.2.0/PagesRoute.html b/api/7.2.0/PagesRoute.html new file mode 100644 index 0000000000..9343ebb529 --- /dev/null +++ b/api/7.2.0/PagesRoute.html @@ -0,0 +1,3 @@ +Interface: PagesRoute
On this page

PagesRoute

Properties
NameTypeDescription
handlerfunction

The route handler that is an async function.

methodString

The route method, e.g. 'GET' or 'POST'.

pathString

The route path.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.AfterFindRequest.html b/api/7.2.0/Parse.Cloud.AfterFindRequest.html new file mode 100644 index 0000000000..31c3afec80 --- /dev/null +++ b/api/7.2.0/Parse.Cloud.AfterFindRequest.html @@ -0,0 +1,3 @@ +Interface: AfterFindRequest
On this page

Parse.Cloud. AfterFindRequest

Properties
NameTypeDescription
installationIdString

If set, the installationId triggering the request.

masterBoolean

If true, means the master key was used.

userParse.User

If set, the user that made the request.

queryParse.Query

The query triggering the hook.

resultsArray.<Parse.Object>

The results the query yielded.

ipString

The IP address of the client making the request.

headersObject

The original HTTP headers for the request.

triggerNameString

The name of the trigger (beforeSave, afterSave, ...)

logObject

The current logger inside Parse Server.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.BeforeFindRequest.html b/api/7.2.0/Parse.Cloud.BeforeFindRequest.html new file mode 100644 index 0000000000..021b50264c --- /dev/null +++ b/api/7.2.0/Parse.Cloud.BeforeFindRequest.html @@ -0,0 +1,3 @@ +Interface: BeforeFindRequest
On this page

Parse.Cloud. BeforeFindRequest

Properties
NameTypeDescription
installationIdString

If set, the installationId triggering the request.

masterBoolean

If true, means the master key was used.

userParse.User

If set, the user that made the request.

queryParse.Query

The query triggering the hook.

ipString

The IP address of the client making the request.

headersObject

The original HTTP headers for the request.

triggerNameString

The name of the trigger (beforeSave, afterSave, ...)

logObject

The current logger inside Parse Server.

isGetBoolean

wether the query a get or a find

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.ConnectTriggerRequest.html b/api/7.2.0/Parse.Cloud.ConnectTriggerRequest.html new file mode 100644 index 0000000000..74b6d8b387 --- /dev/null +++ b/api/7.2.0/Parse.Cloud.ConnectTriggerRequest.html @@ -0,0 +1,3 @@ +Interface: ConnectTriggerRequest
On this page

Parse.Cloud. ConnectTriggerRequest

Properties
NameTypeDescription
installationIdString

If set, the installationId triggering the request.

useMasterKeyBoolean

If true, means the master key was used.

userParse.User

If set, the user that made the request.

clientsInteger

The number of clients connected.

subscriptionsInteger

The number of subscriptions connected.

sessionTokenString

If set, the session of the user that made the request.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.FileTriggerRequest.html b/api/7.2.0/Parse.Cloud.FileTriggerRequest.html new file mode 100644 index 0000000000..faf892a578 --- /dev/null +++ b/api/7.2.0/Parse.Cloud.FileTriggerRequest.html @@ -0,0 +1,3 @@ +Interface: FileTriggerRequest
On this page

Parse.Cloud. FileTriggerRequest

Properties
NameTypeDescription
installationIdString

If set, the installationId triggering the request.

masterBoolean

If true, means the master key was used.

userParse.User

If set, the user that made the request.

fileParse.File

The file that triggered the hook.

fileSizeInteger

The size of the file in bytes.

contentLengthInteger

The value from Content-Length header

ipString

The IP address of the client making the request.

headersObject

The original HTTP headers for the request.

triggerNameString

The name of the trigger (beforeSave, afterSave)

logObject

The current logger inside Parse Server.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.FunctionRequest.html b/api/7.2.0/Parse.Cloud.FunctionRequest.html new file mode 100644 index 0000000000..68fd33416d --- /dev/null +++ b/api/7.2.0/Parse.Cloud.FunctionRequest.html @@ -0,0 +1,3 @@ +Interface: FunctionRequest
On this page

Parse.Cloud. FunctionRequest

Properties
NameTypeDescription
installationIdString

If set, the installationId triggering the request.

masterBoolean

If true, means the master key was used.

userParse.User

If set, the user that made the request.

paramsObject

The params passed to the cloud function.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.JobRequest.html b/api/7.2.0/Parse.Cloud.JobRequest.html new file mode 100644 index 0000000000..ff84b224e1 --- /dev/null +++ b/api/7.2.0/Parse.Cloud.JobRequest.html @@ -0,0 +1,3 @@ +Interface: JobRequest
On this page

Parse.Cloud. JobRequest

Properties
NameTypeDescription
paramsObject

The params passed to the background job.

messagefunction

If message is called with a string argument, will update the current message to be stored in the job status.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.LiveQueryEventTrigger.html b/api/7.2.0/Parse.Cloud.LiveQueryEventTrigger.html new file mode 100644 index 0000000000..21ffc6512c --- /dev/null +++ b/api/7.2.0/Parse.Cloud.LiveQueryEventTrigger.html @@ -0,0 +1,3 @@ +Interface: LiveQueryEventTrigger
On this page

Parse.Cloud. LiveQueryEventTrigger

Properties
NameTypeDescription
installationIdString

If set, the installationId triggering the request.

useMasterKeyBoolean

If true, means the master key was used.

userParse.User

If set, the user that made the request.

sessionTokenString

If set, the session of the user that made the request.

eventString

The live query event that triggered the request.

objectParse.Object

The object triggering the hook.

originalParse.Object

If set, the object, as currently stored.

clientsInteger

The number of clients connected.

subscriptionsInteger

The number of subscriptions connected.

sendEventBoolean

If the LiveQuery event should be sent to the client. Set to false to prevent LiveQuery from pushing to the client.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.TriggerRequest.html b/api/7.2.0/Parse.Cloud.TriggerRequest.html new file mode 100644 index 0000000000..f6d90d62eb --- /dev/null +++ b/api/7.2.0/Parse.Cloud.TriggerRequest.html @@ -0,0 +1,3 @@ +Interface: TriggerRequest
On this page

Parse.Cloud. TriggerRequest

Properties
NameTypeDescription
installationIdString

If set, the installationId triggering the request.

masterBoolean

If true, means the master key was used.

isChallengeBoolean

If true, means the current request is originally triggered by an auth challenge.

userParse.User

If set, the user that made the request.

objectParse.Object

The object triggering the hook.

ipString

The IP address of the client making the request. To ensure retrieving the correct IP address, set the Parse Server option trustProxy: true if Parse Server runs behind a proxy server, for example behind a load balancer.

headersObject

The original HTTP headers for the request.

triggerNameString

The name of the trigger (beforeSave, afterSave, ...)

logObject

The current logger inside Parse Server.

originalParse.Object

If set, the object, as currently stored.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.ValidatorObject.html b/api/7.2.0/Parse.Cloud.ValidatorObject.html new file mode 100644 index 0000000000..aca8335afd --- /dev/null +++ b/api/7.2.0/Parse.Cloud.ValidatorObject.html @@ -0,0 +1,3 @@ +Interface: ValidatorObject
On this page

Parse.Cloud. ValidatorObject

Properties
NameTypeDescription
requireUserBoolean

whether the cloud trigger requires a user.

requireMasterBoolean

whether the cloud trigger requires a master key.

validateMasterKeyBoolean

whether the validator should run if masterKey is provided. Defaults to false.

skipWithMasterKeyBoolean

whether the cloud code function should be ignored using a masterKey.

requireUserKeysArray.<String> | Object

If set, keys required on request.user to make the request.

Properties
NameTypeDescription
fieldString

If requireUserKeys is an object, name of field to validate on request user

Properties
NameTypeDescription
optionsArray | function | Any

array of options that the field can be, function to validate field, or single value. Throw an error if value is invalid.

errorString

custom error message if field is invalid.

requireAnyUserRolesArray.<String> | function

If set, request.user has to be part of at least one roles name to make the request. If set to a function, function must return role names.

requireAllUserRolesArray.<String> | function

If set, request.user has to be part all roles name to make the request. If set to a function, function must return role names.

fieldsObject | Array.<String>

if an array of strings, validator will look for keys in request.params, and throw if not provided. If Object, fields to validate. If the trigger is a cloud function, request.params will be validated, otherwise request.object.

Properties
NameTypeDescription
fieldString

name of field to validate.

Properties
NameTypeDescription
typeString

expected type of data for field.

constantBoolean

whether the field can be modified on the object.

defaultAny

default value if field is null, or initial value constant is true.

optionsArray | function | Any

array of options that the field can be, function to validate field, or single value. Throw an error if value is invalid.

errorString

custom error message if field is invalid.

\ No newline at end of file diff --git a/api/7.2.0/Parse.Cloud.html b/api/7.2.0/Parse.Cloud.html new file mode 100644 index 0000000000..022b9dc7fa --- /dev/null +++ b/api/7.2.0/Parse.Cloud.html @@ -0,0 +1,100 @@ +Namespace: Cloud
On this page

Parse. Cloud

The Parse Cloud Code SDK.

Interfaces

AfterFindRequest
BeforeFindRequest
ConnectTriggerRequest
FileTriggerRequest
FunctionRequest
JobRequest
LiveQueryEventTrigger
TriggerRequest
ValidatorObject

Methods

(static) afterDelete(arg1, func, validator)

Registers an after delete function.

Available in Cloud Code only.

If you want to use afterDelete for a predefined class in the Parse JavaScript SDK (e.g. Parse.User or Parse.File), you should pass the class itself and not the String for arg1.

Parse.Cloud.afterDelete('MyCustomClass', async (request) => {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.afterDelete(Parse.User, async (request) => {
+  // code here
+}, { ...validationObject });
+
Parameters:
NameTypeDescription
arg1String | Parse.Object

The Parse.Object subclass to register the after delete function for. This can instead be a String that is the className of the subclass.

funcfunction

The function to run after a delete. This function can be async and should take just one parameter, Parse.Cloud.TriggerRequest.

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.TriggerRequest, or a Parse.Cloud.ValidatorObject.

(static) afterFind(arg1, func, validator)

Registers an after find function.

Available in Cloud Code only.

If you want to use afterFind for a predefined class in the Parse JavaScript SDK (e.g. Parse.User or Parse.File), you should pass the class itself and not the String for arg1.

Parse.Cloud.afterFind('MyCustomClass', async (request) => {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.afterFind(Parse.User, async (request) => {
+  // code here
+}, { ...validationObject });
+
Parameters:
NameTypeDescription
arg1String | Parse.Object

The Parse.Object subclass to register the after find function for. This can instead be a String that is the className of the subclass.

funcfunction

The function to run before a find. This function can be async and should take just one parameter, Parse.Cloud.AfterFindRequest.

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.AfterFindRequest, or a Parse.Cloud.ValidatorObject.

(static) afterLiveQueryEvent(arg1, func, validator)

Registers an after live query server event function.

Available in Cloud Code only.

Parse.Cloud.afterLiveQueryEvent('MyCustomClass', (request) => {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.afterLiveQueryEvent('MyCustomClass', (request) => {
+  // code here
+}, { ...validationObject });
+
Parameters:
NameTypeDescription
arg1String | Parse.Object

The Parse.Object subclass to register the after live query event function for. This can instead be a String that is the className of the subclass.

funcfunction

The function to run after a live query event. This function can be async and should take one parameter, a Parse.Cloud.LiveQueryEventTrigger.

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.LiveQueryEventTrigger, or a Parse.Cloud.ValidatorObject.

(static) afterLogin(func)

Registers the after login function.

Available in Cloud Code only.

This function is triggered after a user logs in successfully, and after a _Session object has been created.

Parse.Cloud.afterLogin((request) => {
+  // code here
+});
+
Parameters:
NameTypeDescription
funcfunction

The function to run after a login. This function can be async and should take one parameter a Parse.Cloud.TriggerRequest;

(static) afterLogout(func)

Registers the after logout function.

Available in Cloud Code only.

This function is triggered after a user logs out.

Parse.Cloud.afterLogout((request) => {
+  // code here
+});
+
Parameters:
NameTypeDescription
funcfunction

The function to run after a logout. This function can be async and should take one parameter a Parse.Cloud.TriggerRequest;

(static) afterSave(arg1, func, validator)

Registers an after save function.

Available in Cloud Code only.

If you want to use afterSave for a predefined class in the Parse JavaScript SDK (e.g. Parse.User or Parse.File), you should pass the class itself and not the String for arg1.

Parse.Cloud.afterSave('MyCustomClass', async function(request) {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.afterSave(Parse.User, async function(request) {
+  // code here
+}, { ...validationObject });
+
Parameters:
NameTypeDescription
arg1String | Parse.Object

The Parse.Object subclass to register the after save function for. This can instead be a String that is the className of the subclass.

funcfunction

The function to run after a save. This function can be an async function and should take just one parameter, Parse.Cloud.TriggerRequest.

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.TriggerRequest, or a Parse.Cloud.ValidatorObject.

(static) beforeConnect(func, validator)

Registers a before live query server connect function.

Available in Cloud Code only.

Parse.Cloud.beforeConnect(async (request) => {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.beforeConnect(async (request) => {
+  // code here
+}, { ...validationObject });
+
Parameters:
NameTypeDescription
funcfunction

The function to before connection is made. This function can be async and should take just one parameter, Parse.Cloud.ConnectTriggerRequest.

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.ConnectTriggerRequest, or a Parse.Cloud.ValidatorObject.

(static) beforeDelete(arg1, func, validator)

Registers a before delete function.

Available in Cloud Code only.

If you want to use beforeDelete for a predefined class in the Parse JavaScript SDK (e.g. Parse.User or Parse.File), you should pass the class itself and not the String for arg1.

Parse.Cloud.beforeDelete('MyCustomClass', (request) => {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.beforeDelete(Parse.User, (request) => {
+  // code here
+}, { ...validationObject })
+
Parameters:
NameTypeDescription
arg1String | Parse.Object

The Parse.Object subclass to register the before delete function for. This can instead be a String that is the className of the subclass.

funcfunction

The function to run before a delete. This function can be async and should take one parameter, a Parse.Cloud.TriggerRequest.

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.TriggerRequest, or a Parse.Cloud.ValidatorObject.

(static) beforeFind(arg1, func, validator)

Registers a before find function.

Available in Cloud Code only.

If you want to use beforeFind for a predefined class in the Parse JavaScript SDK (e.g. Parse.User or Parse.File), you should pass the class itself and not the String for arg1.

Parse.Cloud.beforeFind('MyCustomClass', async (request) => {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.beforeFind(Parse.User, async (request) => {
+  // code here
+}, { ...validationObject });
+
Parameters:
NameTypeDescription
arg1String | Parse.Object

The Parse.Object subclass to register the before find function for. This can instead be a String that is the className of the subclass.

funcfunction

The function to run before a find. This function can be async and should take just one parameter, Parse.Cloud.BeforeFindRequest.

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.BeforeFindRequest, or a Parse.Cloud.ValidatorObject.

(static) beforeLogin(func)

Registers the before login function.

Available in Cloud Code only.

This function provides further control in validating a login attempt. Specifically, it is triggered after a user enters correct credentials (or other valid authData), but prior to a session being generated.

Parse.Cloud.beforeLogin((request) => {
+  // code here
+})
+
+
Parameters:
NameTypeDescription
funcfunction

The function to run before a login. This function can be async and should take one parameter a Parse.Cloud.TriggerRequest;

(static) beforeSave(arg1, func, validator)

Registers a before save function.

Available in Cloud Code only.

If you want to use beforeSave for a predefined class in the Parse JavaScript SDK (e.g. Parse.User or Parse.File), you should pass the class itself and not the String for arg1.

Parse.Cloud.beforeSave('MyCustomClass', (request) => {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.beforeSave(Parse.User, (request) => {
+  // code here
+}, { ...validationObject })
+
Parameters:
NameTypeDescription
arg1String | Parse.Object

The Parse.Object subclass to register the after save function for. This can instead be a String that is the className of the subclass.

funcfunction

The function to run before a save. This function can be async and should take one parameter a Parse.Cloud.TriggerRequest;

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.TriggerRequest, or a Parse.Cloud.ValidatorObject.

(static) beforeSubscribe(arg1, func, validator)

Registers a before live query subscription function.

Available in Cloud Code only.

If you want to use beforeSubscribe for a predefined class in the Parse JavaScript SDK (e.g. Parse.User or Parse.File), you should pass the class itself and not the String for arg1.

Parse.Cloud.beforeSubscribe('MyCustomClass', (request) => {
+  // code here
+}, (request) => {
+  // validation code here
+});
+
+Parse.Cloud.beforeSubscribe(Parse.User, (request) => {
+  // code here
+}, { ...validationObject });
+
Parameters:
NameTypeDescription
arg1String | Parse.Object

The Parse.Object subclass to register the before subscription function for. This can instead be a String that is the className of the subclass.

funcfunction

The function to run before a subscription. This function can be async and should take one parameter, a Parse.Cloud.TriggerRequest.

validatorObject | function

An optional function to help validating cloud code. This function can be an async function and should take one parameter a Parse.Cloud.TriggerRequest, or a Parse.Cloud.ValidatorObject.

(static) job(name, func)

Defines a Background Job.

Available in Cloud Code only.

Parameters:
NameTypeDescription
nameString

The name of the Background Job

funcfunction

The Background Job to register. This function can be async should take a single parameters a Parse.Cloud.JobRequest

(static) sendEmail(data)

Sends an email through the Parse Server mail adapter.

Available in Cloud Code only. Requires a mail adapter to be configured for Parse Server.

Parse.Cloud.sendEmail({
+  from: 'Example <test@example.com>',
+  to: 'contact@example.com',
+  subject: 'Test email',
+  text: 'This email is a test.'
+});
+
Parameters:
NameTypeDescription
dataObject

The object of the mail data to send.

\ No newline at end of file diff --git a/api/7.2.0/Parse.html b/api/7.2.0/Parse.html new file mode 100644 index 0000000000..64b87b014a --- /dev/null +++ b/api/7.2.0/Parse.html @@ -0,0 +1,3 @@ +Namespace: Parse
On this page

Parse

\ No newline at end of file diff --git a/api/7.2.0/ParseAuthResponse.html b/api/7.2.0/ParseAuthResponse.html new file mode 100644 index 0000000000..aa7647a64f --- /dev/null +++ b/api/7.2.0/ParseAuthResponse.html @@ -0,0 +1,3 @@ +Interface: ParseAuthResponse
On this page

ParseAuthResponse

Properties
NameTypeAttributesDescription
doNotSaveBoolean<optional>

If true, Parse Server will not save provided authData.

responseObject<optional>

If set, Parse Server will send the provided response to the client under authDataResponse

saveObject<optional>

If set, Parse Server will save the object provided into this key, instead of client provided authData

\ No newline at end of file diff --git a/api/7.2.0/ParseServer.html b/api/7.2.0/ParseServer.html new file mode 100644 index 0000000000..6880b3e65a --- /dev/null +++ b/api/7.2.0/ParseServer.html @@ -0,0 +1,3 @@ +Class: ParseServer
On this page

ParseServer

new ParseServer(options)

Parameters:
NameTypeDescription
optionsParseServerOptions

the parse server initialization options

Methods

(async) start()

Starts Parse Server as an express app; this promise resolves when Parse Server is ready to accept requests.

(async) startApp(options) → {ParseServer}

starts the parse server's express app

Parameters:
NameTypeDescription
optionsParseServerOptions

to use to start the server

Returns:

the parse server instance

Type: 
ParseServer

(static) app(options)

Parameters:
NameTypeDescription
optionsObject

let you specify the maxUploadSize when creating the express app

(async, static) createLiveQueryServer(httpServer, config, options) → {Promise.<ParseLiveQueryServer>}

Helper method to create a liveQuery server

Parameters:
NameTypeDescription
httpServerServer

an optional http server to pass

configLiveQueryServerOptions

options for the liveQueryServer

optionsParseServerOptions

options for the ParseServer

Returns:

the live query server instance

Type: 
Promise.<ParseLiveQueryServer>

(async, static) startApp(options) → {ParseServer}

Creates a new ParseServer and starts it.

Parameters:
NameTypeDescription
optionsParseServerOptions

used to start the server

Returns:

the parse server instance

Type: 
ParseServer
\ No newline at end of file diff --git a/api/7.2.0/ParseServerOptions.html b/api/7.2.0/ParseServerOptions.html new file mode 100644 index 0000000000..5da4ffbcbd --- /dev/null +++ b/api/7.2.0/ParseServerOptions.html @@ -0,0 +1,3 @@ +Interface: ParseServerOptions
On this page

ParseServerOptions

Properties
NameTypeDescription
accountLockoutAccountLockoutOptions

The account lockout policy for failed login attempts.

allowClientClassCreationBoolean

Enable (or disable) client class creation, defaults to false

allowCustomObjectIdBoolean

Enable (or disable) custom objectId

allowExpiredAuthDataTokenBoolean

Allow a user to log in even if the 3rd party authentication token that was used to sign in to their account has expired. If this is set to false, then the token will be validated every time the user signs in to their account. This refers to the token that is stored in the _User.authData field. Defaults to false.

allowHeadersArray.<String>

Add headers to Access-Control-Allow-Headers

allowOriginString | Array.<String>

Sets origins for Access-Control-Allow-Origin. This can be a string for a single origin or an array of strings for multiple origins.

analyticsAdapterAdapter.<AnalyticsAdapter>

Adapter module for the analytics

appIdString

Your Parse Application ID

appNameString

Sets the app name

authObject

Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication

cacheAdapterAdapter.<CacheAdapter>

Adapter module for the cache

cacheMaxSizeNumber

Sets the maximum size for the in memory cache, defaults to 10000

cacheTTLNumber

Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds)

clientKeyString

Key for iOS, MacOS, tvOS clients

cloudString

Full path to your cloud code main.js

clusterNumber | Boolean

Run with cluster, optionally set the number of processes default to os.cpus().length

collectionPrefixString

A collection prefix for the classes

convertEmailToLowercaseBoolean

Optional. If set to true, the email property of a user is automatically converted to lowercase before being stored in the database. Consequently, queries must match the case as stored in the database, which would be lowercase in this scenario. If false, the email property is stored as set, without any case modifications. Default is false.

convertUsernameToLowercaseBoolean

Optional. If set to true, the username property of a user is automatically converted to lowercase before being stored in the database. Consequently, queries must match the case as stored in the database, which would be lowercase in this scenario. If false, the username property is stored as set, without any case modifications. Default is false.

customPagesCustomPagesOptions

custom pages for password validation and reset

databaseAdapterAdapter.<StorageAdapter>

Adapter module for the database; any options that are not explicitly described here are passed directly to the database client.

databaseOptionsDatabaseOptions

Options to pass to the database client

databaseURIString

The full URI to your database. Supported databases are mongodb or postgres.

defaultLimitNumber

Default value for limit option on queries, defaults to 100.

directAccessBoolean

Set to true if Parse requests within the same Node.js environment as Parse Server should be routed to Parse Server directly instead of via the HTTP interface. Default is false.

If set to false then Parse requests within the same Node.js environment as Parse Server are executed as HTTP requests sent to Parse Server via the serverURL. For example, a Parse.Query in Cloud Code is calling Parse Server via a HTTP request. The server is essentially making a HTTP request to itself, unnecessarily using network resources such as network ports.

⚠️ In environments where multiple Parse Server instances run behind a load balancer and Parse requests within the current Node.js environment should be routed via the load balancer and distributed as HTTP requests among all instances via the serverURL, this should be set to false.

dotNetKeyString

Key for Unity and .Net SDK

emailAdapterAdapter.<MailAdapter>

Adapter module for email sending

emailVerifyTokenReuseIfValidBoolean

Set to true if a email verification token should be reused in case another token is requested but there is a token that is still valid, i.e. has not expired. This avoids the often observed issue that a user requests multiple emails and does not know which link contains a valid token because each newly generated token would invalidate the previous token.

Default is false.
Requires option verifyUserEmails: true.

emailVerifyTokenValidityDurationNumber

Set the validity duration of the email verification token in seconds after which the token expires. The token is used in the link that is set in the email. After the token expires, the link becomes invalid and a new link has to be sent. If the option is not set or set to undefined, then the token never expires.

For example, to expire the token after 2 hours, set a value of 7200 seconds (= 60 seconds * 60 minutes * 2 hours).

Default is undefined.
Requires option verifyUserEmails: true.

enableAnonymousUsersBoolean

Enable (or disable) anonymous users, defaults to true

enableCollationCaseComparisonBoolean

Optional. If set to true, the collation rule of case comparison for queries and indexes is enabled. Enable this option to run Parse Server with MongoDB Atlas Serverless or AWS Amazon DocumentDB. If false, the collation rule of case comparison is disabled. Default is false.

enableExpressErrorHandlerBoolean

Enables the default express error handler for all errors

encodeParseObjectInCloudFunctionBoolean

If set to true, a Parse.Object that is in the payload when calling a Cloud Function will be converted to an instance of Parse.Object. If false, the object will not be converted and instead be a plain JavaScript object, which contains the raw data of a Parse.Object but is not an actual instance of Parse.Object. Default is false.

ℹ️ The expected behavior would be that the object is converted to an instance of Parse.Object, so you would normally set this option to true. The default is false because this is a temporary option that has been introduced to avoid a breaking change when fixing a bug where JavaScript objects are not converted to actual instances of Parse.Object.

encryptionKeyString

Key for encrypting your files

enforcePrivateUsersBoolean

Set to true if new users should be created without public read and write access.

expireInactiveSessionsBoolean

Sets whether we should expire the inactive sessions, defaults to true. If false, all new sessions are created with no expiration date.

extendSessionOnUseBoolean

Whether Parse Server should automatically extend a valid session by the sessionLength. In order to reduce the number of session updates in the database, a session will only be extended when a request is received after at least half of the current session's lifetime has passed.

fileKeyString

Key for your files

filesAdapterAdapter.<FilesAdapter>

Adapter module for the files sub-system

fileUploadFileUploadOptions

Options for file uploads

graphQLPathString

Mount path for the GraphQL endpoint, defaults to /graphql

graphQLSchemaString

Full path to your GraphQL custom schema.graphql file

hostString

The host to serve ParseServer on, defaults to 0.0.0.0

idempotencyOptionsIdempotencyOptions

Options for request idempotency to deduplicate identical requests that may be caused by network issues. Caution, this is an experimental feature that may not be appropriate for production.

javascriptKeyString

Key for the Javascript SDK

jsonLogsBoolean

Log as structured JSON objects

liveQueryLiveQueryOptions

parse-server's LiveQuery configuration object

liveQueryServerOptionsLiveQueryServerOptions

Live query server configuration options (will start the liveQuery server)

loggerAdapterAdapter.<LoggerAdapter>

Adapter module for the logging sub-system

logLevelString

Sets the level for logs

logLevelsLogLevels

(Optional) Overrides the log levels used internally by Parse Server to log events.

logsFolderString

Folder for the logs (defaults to './logs'); set to null to disable file based logging

maintenanceKeyString

(Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.

⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes directly in the source code of your current version of Parse Server.

maintenanceKeyIpsArray.<String>

(Optional) Restricts the use of maintenance key permissions to a list of IP addresses or ranges.

This option accepts a list of single IP addresses, for example ['10.0.0.1', '10.0.0.2']. You can also use CIDR notation to specify an IP address range, for example ['10.0.1.0/24'].

Special scenarios:
- Setting an empty array [] means that the maintenance key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.
- Setting ['0.0.0.0/0', '::0'] means to allow any IPv4 and IPv6 address to use the maintenance key and effectively disables the IP filter.

Considerations:
- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, ['0.0.0.0/0'] allows any IPv4 address and blocks every IPv6 address. Conversely, ['::0'] allows any IPv6 address and blocks every IPv4 address.
- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value ['0.0.0.0/0'] allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.
- When setting the option via an environment variable the notation is a comma-separated string, for example "0.0.0.0/0,::0".
- IPv6 zone indices (% suffix) are not supported, for example fe80::1%eth0, fe80::1%1 or ::1%lo.

Defaults to ['127.0.0.1', '::1'] which means that only localhost, the server instance on which Parse Server runs, is allowed to use the maintenance key.

masterKeyString

Your Parse Master Key

masterKeyIpsArray.<String>

(Optional) Restricts the use of master key permissions to a list of IP addresses or ranges.

This option accepts a list of single IP addresses, for example ['10.0.0.1', '10.0.0.2']. You can also use CIDR notation to specify an IP address range, for example ['10.0.1.0/24'].

Special scenarios:
- Setting an empty array [] means that the master key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.
- Setting ['0.0.0.0/0', '::0'] means to allow any IPv4 and IPv6 address to use the master key and effectively disables the IP filter.

Considerations:
- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, ['0.0.0.0/0'] allows any IPv4 address and blocks every IPv6 address. Conversely, ['::0'] allows any IPv6 address and blocks every IPv4 address.
- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value ['0.0.0.0/0'] allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.
- When setting the option via an environment variable the notation is a comma-separated string, for example "0.0.0.0/0,::0".
- IPv6 zone indices (% suffix) are not supported, for example fe80::1%eth0, fe80::1%1 or ::1%lo.

Defaults to ['127.0.0.1', '::1'] which means that only localhost, the server instance on which Parse Server runs, is allowed to use the master key.

maxLimitNumber

Max value for limit option on queries, defaults to unlimited

maxLogFilesNumber | String

Maximum number of logs to keep. If not set, no logs will be removed. This can be a number of files or number of days. If using days, add 'd' as the suffix. (default: null)

maxUploadSizeString

Max file size for uploads, defaults to 20mb

middlewareUnion

middleware for express server, can be string or function

mountGraphQLBoolean

Mounts the GraphQL endpoint

mountPathString

Mount path for the server, defaults to /parse

mountPlaygroundBoolean

Mounts the GraphQL Playground - never use this option in production

objectIdSizeNumber

Sets the number of characters in generated object id's, default 10

pagesPagesOptions

The options for pages such as password reset and email verification. Caution, this is an experimental feature that may not be appropriate for production.

passwordPolicyPasswordPolicyOptions

The password policy for enforcing password related rules.

playgroundPathString

Mount path for the GraphQL Playground, defaults to /playground

portNumber

The port to run the ParseServer, defaults to 1337.

preserveFileNameBoolean

Enable (or disable) the addition of a unique hash to the file names

preventLoginWithUnverifiedEmailBoolean

Set to true to prevent a user from logging in if the email has not yet been verified and email verification is required.

Default is false.
Requires option verifyUserEmails: true.

preventSignupWithUnverifiedEmailBoolean

If set to true it prevents a user from signing up if the email has not yet been verified and email verification is required. In that case the server responds to the sign-up with HTTP status 400 and a Parse Error 205 EMAIL_NOT_FOUND. If set to false the server responds with HTTP status 200, and client SDKs return an unauthenticated Parse User without session token. In that case subsequent requests fail until the user's email address is verified.

Default is false.
Requires option verifyUserEmails: true.

protectedFieldsProtectedFields

Protected fields that should be treated with extra security when fetching details.

publicServerURLString

Public URL to your parse server with http:// or https://.

pushAny

Configuration for push, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#push-notifications

rateLimitArray.<RateLimitOptions>

Options to limit repeated requests to Parse Server APIs. This can be used to protect sensitive endpoints such as /requestPasswordReset from brute-force attacks or Parse Server as a whole from denial-of-service (DoS) attacks.

ℹ️ Mind the following limitations:
- rate limits applied per IP address; this limits protection against distributed denial-of-service (DDoS) attacks where many requests are coming from various IP addresses
- if multiple Parse Server instances are behind a load balancer or ran in a cluster, each instance will calculate it's own request rates, independent from other instances; this limits the applicability of this feature when using a load balancer and another rate limiting solution that takes requests across all instances into account may be more suitable
- this feature provides basic protection against denial-of-service attacks, but a more sophisticated solution works earlier in the request flow and prevents a malicious requests to even reach a server instance; it's therefore recommended to implement a solution according to architecture and user case.

readOnlyMasterKeyString

Read-only key, which has the same capabilities as MasterKey without writes

requestKeywordDenylistArray.<RequestKeywordDenylist>

An array of keys and values that are prohibited in database read and write requests to prevent potential security vulnerabilities. It is possible to specify only a key ({"key":"..."}), only a value ({"value":"..."}) or a key-value pair ({"key":"...","value":"..."}). The specification can use the following types: boolean, numeric or string, where string will be interpreted as a regex notation. Request data is deep-scanned for matching definitions to detect also any nested occurrences. Defaults are patterns that are likely to be used in malicious requests. Setting this option will override the default patterns.

restAPIKeyString

Key for REST calls

revokeSessionOnPasswordResetBoolean

When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.

scheduledPushBoolean

Configuration for push scheduling, defaults to false.

schemaSchemaOptions

Defined schema

securitySecurityOptions

The security options to identify and report weak security settings.

sendUserEmailVerificationBoolean

Set to false to prevent sending of verification email. Supports a function with a return value of true or false for conditional email sending.

Default is true.

serverCloseCompletefunction

Callback when server has closed

serverURLString

URL to your parse server with http:// or https://.

sessionLengthNumber

Session duration, in seconds, defaults to 1 year

silentBoolean

Disables console output

startLiveQueryServerBoolean

Starts the liveQuery server

trustProxyAny

The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the express trust proxy settings documentation. Defaults to false.

userSensitiveFieldsArray.<String>

Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields

verboseBoolean

Set the logging to verbose

verifyUserEmailsBoolean

Set to true to require users to verify their email address to complete the sign-up process. Supports a function with a return value of true or false for conditional verification.

Default is false.

webhookKeyString

Key sent with outgoing webhook calls

\ No newline at end of file diff --git a/api/7.2.0/PasswordPolicyOptions.html b/api/7.2.0/PasswordPolicyOptions.html new file mode 100644 index 0000000000..b1648f2ec9 --- /dev/null +++ b/api/7.2.0/PasswordPolicyOptions.html @@ -0,0 +1,3 @@ +Interface: PasswordPolicyOptions
On this page

PasswordPolicyOptions

Properties
NameTypeDescription
doNotAllowUsernameBoolean

Set to true to disallow the username as part of the password.

Default is false.

maxPasswordAgeNumber

Set the number of days after which a password expires. Login attempts fail if the user does not reset the password before expiration.

maxPasswordHistoryNumber

Set the number of previous password that will not be allowed to be set as new password. If the option is not set or set to 0, no previous passwords will be considered.

Valid values are >= 0 and <= 20.
Default is 0.

resetPasswordSuccessOnInvalidEmailBoolean

Set to true if a request to reset the password should return a success response even if the provided email address is invalid, or false if the request should return an error response if the email address is invalid.

Default is true.

resetTokenReuseIfValidBoolean

Set to true if a password reset token should be reused in case another token is requested but there is a token that is still valid, i.e. has not expired. This avoids the often observed issue that a user requests multiple emails and does not know which link contains a valid token because each newly generated token would invalidate the previous token.

Default is false.

resetTokenValidityDurationNumber

Set the validity duration of the password reset token in seconds after which the token expires. The token is used in the link that is set in the email. After the token expires, the link becomes invalid and a new link has to be sent. If the option is not set or set to undefined, then the token never expires.

For example, to expire the token after 2 hours, set a value of 7200 seconds (= 60 seconds * 60 minutes * 2 hours).

Default is undefined.

validationErrorString

Set the error message to be sent.

Default is Password does not meet the Password Policy requirements.

validatorCallbackfunction

Set a callback function to validate a password to be accepted.

If used in combination with validatorPattern, the password must pass both to be accepted.

validatorPatternString

Set the regular expression validation pattern a password must match to be accepted.

If used in combination with validatorCallback, the password must pass both to be accepted.

\ No newline at end of file diff --git a/api/7.2.0/PubSubAdapter.Publisher.html b/api/7.2.0/PubSubAdapter.Publisher.html new file mode 100644 index 0000000000..edb4f8228c --- /dev/null +++ b/api/7.2.0/PubSubAdapter.Publisher.html @@ -0,0 +1,3 @@ +Interface: Publisher
On this page

Publisher

\ No newline at end of file diff --git a/api/7.2.0/PubSubAdapter.Subscriber.html b/api/7.2.0/PubSubAdapter.Subscriber.html new file mode 100644 index 0000000000..0a4feb154b --- /dev/null +++ b/api/7.2.0/PubSubAdapter.Subscriber.html @@ -0,0 +1,3 @@ +Interface: Subscriber
On this page

Subscriber

\ No newline at end of file diff --git a/api/7.2.0/RateLimitOptions.html b/api/7.2.0/RateLimitOptions.html new file mode 100644 index 0000000000..ce05c0fbe7 --- /dev/null +++ b/api/7.2.0/RateLimitOptions.html @@ -0,0 +1,3 @@ +Interface: RateLimitOptions
On this page

RateLimitOptions

Properties
NameTypeDescription
errorResponseMessageString

The error message that should be returned in the body of the HTTP 429 response when the rate limit is hit. Default is Too many requests..

includeInternalRequestsBoolean

Optional, if true the rate limit will also apply to requests that are made in by Cloud Code, default is false. Note that a public Cloud Code function that triggers internal requests may circumvent rate limiting and be vulnerable to attacks.

includeMasterKeyBoolean

Optional, if true the rate limit will also apply to requests using the masterKey, default is false. Note that a public Cloud Code function that triggers internal requests using the masterKey may circumvent rate limiting and be vulnerable to attacks.

redisUrlString

Optional, the URL of the Redis server to store rate limit data. This allows to rate limit requests for multiple servers by calculating the sum of all requests across all servers. This is useful if multiple servers are processing requests behind a load balancer. For example, the limit of 10 requests is reached if each of 2 servers processed 5 requests.

requestCountNumber

The number of requests that can be made per IP address within the time window set in requestTimeWindow before the rate limit is applied.

requestMethodsArray.<String>

Optional, the HTTP request methods to which the rate limit should be applied, default is all methods.

requestPathString

The path of the API route to be rate limited. Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings, string patterns, or regular expression. See: https://expressjs.com/en/guide/routing.html

requestTimeWindowNumber

The window of time in milliseconds within which the number of requests set in requestCount can be made before the rate limit is applied.

zoneString

The type of rate limit to apply. The following types are supported:

- global: rate limit based on the number of requests made by all users
- ip: rate limit based on the IP address of the request
- user: rate limit based on the user ID of the request
- session: rate limit based on the session token of the request


:default: 'ip'

\ No newline at end of file diff --git a/api/7.2.0/SchemaOptions.html b/api/7.2.0/SchemaOptions.html new file mode 100644 index 0000000000..0a2ca7725b --- /dev/null +++ b/api/7.2.0/SchemaOptions.html @@ -0,0 +1,3 @@ +Interface: SchemaOptions
On this page

SchemaOptions

Properties
NameTypeDescription
afterMigrationfunction

Execute a callback after running schema migrations.

beforeMigrationfunction

Execute a callback before running schema migrations.

definitionsAny

Rest representation on Parse.Schema https://docs.parseplatform.org/rest/guide/#adding-a-schema

deleteExtraFieldsBoolean

Is true if Parse Server should delete any fields not defined in a schema definition. This should only be used during development.

lockSchemasBoolean

Is true if Parse Server will reject any attempts to modify the schema while the server is running.

recreateModifiedFieldsBoolean

Is true if Parse Server should recreate any fields that are different between the current database schema and theschema definition. This should only be used during development.

strictBoolean

Is true if Parse Server should exit if schema update fail.

\ No newline at end of file diff --git a/api/7.2.0/SecurityOptions.html b/api/7.2.0/SecurityOptions.html new file mode 100644 index 0000000000..e7b1c659d3 --- /dev/null +++ b/api/7.2.0/SecurityOptions.html @@ -0,0 +1,3 @@ +Interface: SecurityOptions
On this page

SecurityOptions

Properties
NameTypeDescription
checkGroupsArray.<CheckGroup>

The security check groups to run. This allows to add custom security checks or override existing ones. Default are the groups defined in CheckGroups.js.

enableCheckBoolean

Is true if Parse Server should check for weak security settings.

enableCheckLogBoolean

Is true if the security check report should be written to logs. This should only be enabled temporarily to not expose weak security settings in logs.

\ No newline at end of file diff --git a/api/7.2.0/WSAdapter.html b/api/7.2.0/WSAdapter.html new file mode 100644 index 0000000000..bd34567e8b --- /dev/null +++ b/api/7.2.0/WSAdapter.html @@ -0,0 +1,3 @@ +Class: WSAdapter
On this page

WSAdapter

Wrapper for ws node module

Constructor

new WSAdapter()

\ No newline at end of file diff --git a/api/7.2.0/data/search.json b/api/7.2.0/data/search.json new file mode 100644 index 0000000000..a67525713d --- /dev/null +++ b/api/7.2.0/data/search.json @@ -0,0 +1 @@ +{"list":[{"title":"AccountLockoutOptions","link":"AccountLockoutOptions"},{"title":"AuthAdapter","link":"AuthAdapter"},{"title":"AuthAdapter#afterFind","link":"afterFind","description":"

Triggered when auth data is fetched

"},{"title":"AuthAdapter#challenge","link":"challenge","description":"

Triggered in pre authentication process if needed (like webauthn, SMS OTP)

"},{"title":"AuthAdapter#validateAppId","link":"validateAppId"},{"title":"AuthAdapter#validateAuthData","link":"validateAuthData","description":"

Legacy usage, if provided it will be triggered when authData related to this provider is touched (signup/update/login)\notherwise you should implement validateSetup, validateLogin and validateUpdate

"},{"title":"AuthAdapter#validateLogin","link":"validateLogin","description":"

Triggered when user provide authData related to this provider\nThe user is not logged in and has already set this provider before

"},{"title":"AuthAdapter#validateOptions","link":"validateOptions","description":"

Triggered when the adapter is first attached to Parse Server

"},{"title":"AuthAdapter#validateSetUp","link":"validateSetUp","description":"

Triggered when user provide for the first time this auth provider\ncould be a register or the user adding a new auth service

"},{"title":"AuthAdapter#validateUpdate","link":"validateUpdate","description":"

Triggered when user provide authData related to this provider\nthe user is logged in and has already set this provider before

"},{"title":"AuthPolicy","link":"AuthPolicy","description":"

AuthPolicy\ndefault: can be combined with ONE additional auth provider if additional configured on user\nadditional: could be only used with a default policy auth provider\nsolo: Will ignore ALL additional providers if additional configured on user

"},{"title":"CustomPagesOptions","link":"CustomPagesOptions"},{"title":"DatabaseOptions","link":"DatabaseOptions"},{"title":"FileUploadOptions","link":"FileUploadOptions"},{"title":"FilesAdapter","link":"FilesAdapter","description":"

Simple filename validation

"},{"title":"IdempotencyOptions","link":"IdempotencyOptions"},{"title":"LiveQueryOptions","link":"LiveQueryOptions"},{"title":"LiveQueryServerOptions","link":"LiveQueryServerOptions"},{"title":"LogLevels","link":"LogLevels"},{"title":"MongoCollection.caseInsensitiveCollation","link":"caseInsensitiveCollation","description":"

Collation to support case insensitive queries

"},{"title":"PagesCustomUrlsOptions","link":"PagesCustomUrlsOptions"},{"title":"PagesOptions","link":"PagesOptions"},{"title":"PagesRoute","link":"PagesRoute"},{"title":"Parse","link":"Parse","description":"

The Parse SDK.\nsee api docs and guide

"},{"title":"Parse.Cloud","link":"Cloud","description":"

The Parse Cloud Code SDK.

"},{"title":"Parse.Cloud.AfterFindRequest","link":"AfterFindRequest"},{"title":"Parse.Cloud.BeforeFindRequest","link":"BeforeFindRequest"},{"title":"Parse.Cloud.ConnectTriggerRequest","link":"ConnectTriggerRequest"},{"title":"Parse.Cloud.FileTriggerRequest","link":"FileTriggerRequest"},{"title":"Parse.Cloud.FunctionRequest","link":"FunctionRequest"},{"title":"Parse.Cloud.JobRequest","link":"JobRequest"},{"title":"Parse.Cloud.LiveQueryEventTrigger","link":"LiveQueryEventTrigger"},{"title":"Parse.Cloud.ParseCloud.define","link":"define","description":"

Defines a Cloud Function.

\n

Available in Cloud Code only.

\n
Parse.Cloud.define('functionName', (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.define('functionName', (request) => {\n  // code here\n}, { ...validationObject });\n
"},{"title":"Parse.Cloud.TriggerRequest","link":"TriggerRequest"},{"title":"Parse.Cloud.ValidatorObject","link":"ValidatorObject"},{"title":"Parse.Cloud.afterDelete","link":"afterDelete","description":"

Registers an after delete function.

\n

Available in Cloud Code only.

\n

If you want to use afterDelete for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User} or {@link Parse.File}), you should pass the class itself and not the String for arg1.

\n
Parse.Cloud.afterDelete('MyCustomClass', async (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.afterDelete(Parse.User, async (request) => {\n  // code here\n}, { ...validationObject });\n
"},{"title":"Parse.Cloud.afterFind","link":"afterFind","description":"

Registers an after find function.

\n

Available in Cloud Code only.

\n

If you want to use afterFind for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User} or {@link Parse.File}), you should pass the class itself and not the String for arg1.

\n
Parse.Cloud.afterFind('MyCustomClass', async (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.afterFind(Parse.User, async (request) => {\n  // code here\n}, { ...validationObject });\n
"},{"title":"Parse.Cloud.afterLiveQueryEvent","link":"afterLiveQueryEvent","description":"

Registers an after live query server event function.

\n

Available in Cloud Code only.

\n
Parse.Cloud.afterLiveQueryEvent('MyCustomClass', (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.afterLiveQueryEvent('MyCustomClass', (request) => {\n  // code here\n}, { ...validationObject });\n
"},{"title":"Parse.Cloud.afterLogin","link":"afterLogin","description":"

Registers the after login function.

\n

Available in Cloud Code only.

\n

This function is triggered after a user logs in successfully,\nand after a _Session object has been created.

\n
Parse.Cloud.afterLogin((request) => {\n  // code here\n});\n
"},{"title":"Parse.Cloud.afterLogout","link":"afterLogout","description":"

Registers the after logout function.

\n

Available in Cloud Code only.

\n

This function is triggered after a user logs out.

\n
Parse.Cloud.afterLogout((request) => {\n  // code here\n});\n
"},{"title":"Parse.Cloud.afterSave","link":"afterSave","description":"

Registers an after save function.

\n

Available in Cloud Code only.

\n

If you want to use afterSave for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User} or {@link Parse.File}), you should pass the class itself and not the String for arg1.

\n
Parse.Cloud.afterSave('MyCustomClass', async function(request) {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.afterSave(Parse.User, async function(request) {\n  // code here\n}, { ...validationObject });\n
"},{"title":"Parse.Cloud.beforeConnect","link":"beforeConnect","description":"

Registers a before live query server connect function.

\n

Available in Cloud Code only.

\n
Parse.Cloud.beforeConnect(async (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.beforeConnect(async (request) => {\n  // code here\n}, { ...validationObject });\n
"},{"title":"Parse.Cloud.beforeDelete","link":"beforeDelete","description":"

Registers a before delete function.

\n

Available in Cloud Code only.

\n

If you want to use beforeDelete for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User} or {@link Parse.File}), you should pass the class itself and not the String for arg1.

\n
Parse.Cloud.beforeDelete('MyCustomClass', (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.beforeDelete(Parse.User, (request) => {\n  // code here\n}, { ...validationObject })\n
"},{"title":"Parse.Cloud.beforeFind","link":"beforeFind","description":"

Registers a before find function.

\n

Available in Cloud Code only.

\n

If you want to use beforeFind for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User} or {@link Parse.File}), you should pass the class itself and not the String for arg1.

\n
Parse.Cloud.beforeFind('MyCustomClass', async (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.beforeFind(Parse.User, async (request) => {\n  // code here\n}, { ...validationObject });\n
"},{"title":"Parse.Cloud.beforeLogin","link":"beforeLogin","description":"

Registers the before login function.

\n

Available in Cloud Code only.

\n

This function provides further control\nin validating a login attempt. Specifically,\nit is triggered after a user enters\ncorrect credentials (or other valid authData),\nbut prior to a session being generated.

\n
Parse.Cloud.beforeLogin((request) => {\n  // code here\n})\n\n
"},{"title":"Parse.Cloud.beforeSave","link":"beforeSave","description":"

Registers a before save function.

\n

Available in Cloud Code only.

\n

If you want to use beforeSave for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User} or {@link Parse.File}), you should pass the class itself and not the String for arg1.

\n
Parse.Cloud.beforeSave('MyCustomClass', (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.beforeSave(Parse.User, (request) => {\n  // code here\n}, { ...validationObject })\n
"},{"title":"Parse.Cloud.beforeSubscribe","link":"beforeSubscribe","description":"

Registers a before live query subscription function.

\n

Available in Cloud Code only.

\n

If you want to use beforeSubscribe for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User} or {@link Parse.File}), you should pass the class itself and not the String for arg1.

\n
Parse.Cloud.beforeSubscribe('MyCustomClass', (request) => {\n  // code here\n}, (request) => {\n  // validation code here\n});\n\nParse.Cloud.beforeSubscribe(Parse.User, (request) => {\n  // code here\n}, { ...validationObject });\n
"},{"title":"Parse.Cloud.job","link":"job","description":"

Defines a Background Job.

\n

Available in Cloud Code only.

"},{"title":"Parse.Cloud.sendEmail","link":"sendEmail","description":"

Sends an email through the Parse Server mail adapter.

\n

Available in Cloud Code only.\nRequires a mail adapter to be configured for Parse Server.

\n
Parse.Cloud.sendEmail({\n  from: 'Example <test@example.com>',\n  to: 'contact@example.com',\n  subject: 'Test email',\n  text: 'This email is a test.'\n});\n
"},{"title":"Parse.Server.ParseServer.RateLimitZone","link":"ParseServer.RateLimitZone","description":"

...

"},{"title":"ParseAuthResponse","link":"ParseAuthResponse"},{"title":"ParseServer","link":"ParseServer"},{"title":"ParseServer#start","link":"start","description":"

Starts Parse Server as an express app; this promise resolves when Parse Server is ready to accept requests.

"},{"title":"ParseServer#startApp","link":"startApp","description":"

starts the parse server's express app

"},{"title":"ParseServer.app","link":"app"},{"title":"ParseServer.createLiveQueryServer","link":"createLiveQueryServer","description":"

Helper method to create a liveQuery server

"},{"title":"ParseServer.startApp","link":"startApp","description":"

Creates a new ParseServer and starts it.

"},{"title":"ParseServerOptions","link":"ParseServerOptions"},{"title":"PasswordPolicyOptions","link":"PasswordPolicyOptions"},{"title":"PubSubAdapter.Publisher","link":"Publisher"},{"title":"PubSubAdapter.Subscriber","link":"Subscriber"},{"title":"RateLimitOptions","link":"RateLimitOptions"},{"title":"SchemaOptions","link":"SchemaOptions"},{"title":"SecurityOptions","link":"SecurityOptions"},{"title":"WSAdapter","link":"WSAdapter"},{"title":"crypto","link":"crypto","description":"

GridFSBucketAdapter\nStores files in Mongo using GridFS\nRequires the database adapter to be based on mongoclient

"},{"title":"getSigningKey","link":"getSigningKey","description":"

Returns the signing key from a JWKS client.

"},{"title":"module:AdapterLoader","link":"AdapterLoader"},{"title":"module:AdapterLoader.loadAdapter","link":"loadAdapter"},{"title":"module:Adapters","link":"Adapters"},{"title":"module:Adapters\nLogger Adapter\nAllows you to change the logger mechanism\nDefault is WinstonLoggerAdapter.js.LoggerAdapter","link":"LoggerAdapter"},{"title":"module:Adapters\nLogger Adapter\nAllows you to change the logger mechanism\nDefault is WinstonLoggerAdapter.js.LoggerAdapter#log","link":"log","description":"

log

"},{"title":"module:Adapters\nMail Adapter prototype\nA MailAdapter should implement at least sendMail().MailAdapter","link":"MailAdapter"},{"title":"module:Adapters\nMail Adapter prototype\nA MailAdapter should implement at least sendMail().MailAdapter#sendMail","link":"sendMail","description":"

A method for sending mail

"},{"title":"module:Adapters.CacheAdapter","link":"CacheAdapter"},{"title":"module:Adapters.CacheAdapter#clear","link":"clear","description":"

Empty a cache

"},{"title":"module:Adapters.CacheAdapter#del","link":"del","description":"

Remove a value from the cache.

"},{"title":"module:Adapters.CacheAdapter#get","link":"get","description":"

Get a value in the cache

"},{"title":"module:Adapters.CacheAdapter#put","link":"put","description":"

Set a value in the cache

"},{"title":"module:Adapters.FilesAdapter","link":"FilesAdapter"},{"title":"module:Adapters.FilesAdapter#createFile","link":"createFile","description":"

Responsible for storing the file in order to be retrieved later by its filename

"},{"title":"module:Adapters.FilesAdapter#deleteFile","link":"deleteFile","description":"

Responsible for deleting the specified file

"},{"title":"module:Adapters.FilesAdapter#getFileData","link":"getFileData","description":"

Responsible for retrieving the data of the specified file

"},{"title":"module:Adapters.FilesAdapter#getFileLocation","link":"getFileLocation","description":"

Returns an absolute URL where the file can be accessed

"},{"title":"module:Adapters.PubSubAdapter","link":"PubSubAdapter"},{"title":"module:Adapters.PubSubAdapter.createPublisher","link":"createPublisher"},{"title":"module:Adapters.PubSubAdapter.createSubscriber","link":"createSubscriber"},{"title":"module:Adapters.PushAdapter","link":"PushAdapter"},{"title":"module:Adapters.PushAdapter#getValidPushTypes","link":"getValidPushTypes","description":"

Get an array of valid push types.

"},{"title":"module:Adapters.PushAdapter#send","link":"send"},{"title":"module:Adapters.WSSAdapter","link":"WSSAdapter"},{"title":"module:Adapters.WSSAdapter#close","link":"close","description":"

Closes server.

"},{"title":"module:Adapters.WSSAdapter#start","link":"start","description":"

Initialize Connection.

"},{"title":"module:Adapters~AnalyticsAdapter#appOpened","link":"appOpened"},{"title":"module:Adapters~AnalyticsAdapter#trackEvent","link":"trackEvent"}]} \ No newline at end of file diff --git a/api/7.2.0/fonts/Inconsolata-Regular.ttf b/api/7.2.0/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000000..457d262cf5 Binary files /dev/null and b/api/7.2.0/fonts/Inconsolata-Regular.ttf differ diff --git a/api/7.2.0/fonts/OpenSans-Regular.ttf b/api/7.2.0/fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000000..e21ff5f1ec Binary files /dev/null and b/api/7.2.0/fonts/OpenSans-Regular.ttf differ diff --git a/api/7.2.0/fonts/WorkSans-Bold.ttf b/api/7.2.0/fonts/WorkSans-Bold.ttf new file mode 100644 index 0000000000..0caaf4d4dd Binary files /dev/null and b/api/7.2.0/fonts/WorkSans-Bold.ttf differ diff --git a/api/7.2.0/global.html b/api/7.2.0/global.html new file mode 100644 index 0000000000..e7efd4bdfd --- /dev/null +++ b/api/7.2.0/global.html @@ -0,0 +1,3 @@ +Global
On this page

Members

FilesAdapter

Simple filename validation

(constant) crypto

GridFSBucketAdapter Stores files in Mongo using GridFS Requires the database adapter to be based on mongoclient

Methods

(async) getSigningKey(client, key)

Returns the signing key from a JWKS client.

Parameters:
NameTypeDescription
clientObject

The JWKS client.

keyString

The kid.

Type Definitions

AuthPolicy

AuthPolicy default: can be combined with ONE additional auth provider if additional configured on user additional: could be only used with a default policy auth provider solo: Will ignore ALL additional providers if additional configured on user

Type:
  • "default" | "additional" | "solo"
\ No newline at end of file diff --git a/api/7.2.0/index.html b/api/7.2.0/index.html new file mode 100644 index 0000000000..bbe3a1dfd0 --- /dev/null +++ b/api/7.2.0/index.html @@ -0,0 +1,431 @@ +Home
On this page

parse-repository-header-server


Build StatusBuild StatusBuild StatusSnyk BadgeCoverageauto-release

Node VersionMongoDB VersionPostgres Version

npm latest versionnpm beta versionnpm alpha version

Backers on Open CollectiveSponsors on Open CollectiveForumTwitterChat


Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Parse Server works with the Express web application framework. It can be added to existing web applications, or run by itself.

The full documentation for Parse Server is available in the wiki. The Parse Server guide is a good place to get started. An API reference and Cloud Code guide are also available. If you're interested in developing for Parse Server, the Development guide will help you get set up.


A big thank you 🙏 to our sponsors and backers who support the development of Parse Platform!

Bronze Sponsors

Bronze Sponsors


Flavors & Branches

Parse Server is available in different flavors on different branches:

  • The main branches are release, beta and alpha. See the changelog overview for details.
  • The long-term-support (LTS) branches are named release-<version>.x.x, for example release-5.x.x. LTS branches do not have pre-release branches.

Long Term Support

Long-Term-Support (LTS) is provided for the previous Parse Server major version. For example, Parse Server 5.x will receive security updates until Parse Server 6.x is superseded by Parse Server 7.x and becomes the new LTS version. While the current major version is published on branch release, a LTS version is published on branch release-#.x.x, for example release-5.x.x for the Parse Server 5.x LTS branch.

⚠️ LTS versions are provided to help you transition as soon as possible to the current major version. While we aim to fix security vulnerabilities in the LTS version, our main focus is on developing the current major version and preparing the next major release. Therefore we may leave certain vulnerabilities up to the community to fix. Search for pull requests with the specific LTS base branch to see the current open vulnerabilities for that LTS branch.

Getting Started

The fastest and easiest way to get started is to run MongoDB and Parse Server locally.

Running Parse Server

Before you start make sure you have installed:

Compatibility

Node.js

Parse Server is continuously tested with the most recent releases of Node.js to ensure compatibility. We follow the Node.js Long Term Support plan and only test against versions that are officially supported and have not reached their end-of-life date.

VersionLatest VersionEnd-of-LifeCompatible
Node.js 1818.20.0April 2025✅ Yes
Node.js 2020.12.0April 2026✅ Yes

MongoDB

Parse Server is continuously tested with the most recent releases of MongoDB to ensure compatibility. We follow the MongoDB support schedule and MongoDB lifecycle schedule and only test against versions that are officially supported and have not reached their end-of-life date. MongoDB "rapid releases" are ignored as these are considered pre-releases of the next major version.

VersionLatest VersionEnd-of-LifeCompatible
MongoDB 4.24.2.25April 2023✅ Yes
MongoDB 4.44.4.29February 2024✅ Yes
MongoDB 55.0.26October 2024✅ Yes
MongoDB 66.0.14July 2025✅ Yes
MongoDB 77.0.8TDB✅ Yes

PostgreSQL

Parse Server is continuously tested with the most recent releases of PostgreSQL and PostGIS to ensure compatibility, using PostGIS docker images. We follow the PostgreSQL support schedule and PostGIS support schedule and only test against versions that are officially supported and have not reached their end-of-life date. Due to the extensive PostgreSQL support duration of 5 years, Parse Server drops support about 2 years before the official end-of-life date.

VersionPostGIS VersionEnd-of-LifeParse Server SupportCompatible
Postgres 133.1, 3.2, 3.3, 3.4November 2025<= 6.x (2023)✅ Yes
Postgres 143.4November 2026<= 7.x (2024)✅ Yes
Postgres 153.4November 2027<= 8.x (2025)✅ Yes
Postgres 163.4November 2028<= 9.x (2026)✅ Yes

Locally

$ npm install -g parse-server mongodb-runner
+$ mongodb-runner start
+$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test
+

Note: If installation with -g fails due to permission problems (npm ERR! code 'EACCES'), please refer to this link.

Docker Container

$ git clone https://github.com/parse-community/parse-server
+$ cd parse-server
+$ docker build --tag parse-server .
+$ docker run --name my-mongo -d mongo
+

Running the Parse Server Image

$ docker run --name my-parse-server -v config-vol:/parse-server/config -p 1337:1337 --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test
+

Note: If you want to use Cloud Code, add -v cloud-code-vol:/parse-server/cloud --cloud /parse-server/cloud/main.js to the command above. Make sure main.js is in the cloud-code-vol directory before starting Parse Server.

You can use any arbitrary string as your application id and master key. These will be used by your clients to authenticate with the Parse Server.

That's it! You are now running a standalone version of Parse Server on your machine.

Using a remote MongoDB? Pass the --databaseURI DATABASE_URI parameter when starting parse-server. Learn more about configuring Parse Server here. For a full list of available options, run parse-server --help.

Saving an Object

Now that you're running Parse Server, it is time to save your first object. We'll use the REST API, but you can easily do the same using any of the Parse SDKs. Run the following:

$ curl -X POST \
+-H "X-Parse-Application-Id: APPLICATION_ID" \
+-H "Content-Type: application/json" \
+-d '{"score":1337,"playerName":"Sean Plott","cheatMode":false}' \
+http://localhost:1337/parse/classes/GameScore
+

You should get a response similar to this:

{
+  "objectId": "2ntvSpRGIK",
+  "createdAt": "2016-03-11T23:51:48.050Z"
+}
+

You can now retrieve this object directly (make sure to replace 2ntvSpRGIK with the actual objectId you received when the object was created):

$ curl -X GET \
+  -H "X-Parse-Application-Id: APPLICATION_ID" \
+  http://localhost:1337/parse/classes/GameScore/2ntvSpRGIK
+
// Response
+{
+  "objectId": "2ntvSpRGIK",
+  "score": 1337,
+  "playerName": "Sean Plott",
+  "cheatMode": false,
+  "updatedAt": "2016-03-11T23:51:48.050Z",
+  "createdAt": "2016-03-11T23:51:48.050Z"
+}
+

Keeping tracks of individual object ids is not ideal, however. In most cases you will want to run a query over the collection, like so:

$ curl -X GET \
+  -H "X-Parse-Application-Id: APPLICATION_ID" \
+  http://localhost:1337/parse/classes/GameScore
+
// The response will provide all the matching objects within the `results` array:
+{
+  "results": [
+    {
+      "objectId": "2ntvSpRGIK",
+      "score": 1337,
+      "playerName": "Sean Plott",
+      "cheatMode": false,
+      "updatedAt": "2016-03-11T23:51:48.050Z",
+      "createdAt": "2016-03-11T23:51:48.050Z"
+    }
+  ]
+}
+

To learn more about using saving and querying objects on Parse Server, check out the Parse documentation.

Connect an SDK

Parse provides SDKs for all the major platforms. Refer to the Parse Server guide to learn how to connect your app to Parse Server.

Running Parse Server elsewhere

Once you have a better understanding of how the project works, please refer to the Parse Server wiki for in-depth guides to deploy Parse Server to major infrastructure providers. Read on to learn more about additional ways of running Parse Server.

Sample Application

We have provided a basic Node.js application that uses the Parse Server module on Express and can be easily deployed to various infrastructure providers:

Parse Server + Express

You can also create an instance of Parse Server, and mount it on a new or existing Express website:

const express = require('express');
+const ParseServer = require('parse-server').ParseServer;
+const app = express();
+
+const server = new ParseServer({
+  databaseURI: 'mongodb://localhost:27017/dev', // Connection string for your MongoDB database
+  cloud: './cloud/main.js', // Path to your Cloud Code
+  appId: 'myAppId',
+  masterKey: 'myMasterKey', // Keep this key secret!
+  fileKey: 'optionalFileKey',
+  serverURL: 'http://localhost:1337/parse' // Don't forget to change to https if needed
+});
+
+// Start server
+await server.start();
+
+// Serve the Parse API on the /parse URL prefix
+app.use('/parse', server.app);
+
+app.listen(1337, function() {
+  console.log('parse-server-example running on port 1337.');
+});
+

For a full list of available options, run parse-server --help or take a look at Parse Server Configurations.

Parse Server Health

Check the Parse Server health by sending a request to the /parse/health endpoint.

The response looks like this:

{
+  "status": "ok"
+}
+

Status Values

ValueDescription
initializedThe server has been created but the start method has not been called yet.
startingThe server is starting up.
okThe server started and is running.
errorThere was a startup error, see the logs for details.

Configuration

Parse Server can be configured using the following options. You may pass these as parameters when running a standalone parse-server, or by loading a configuration file in JSON format using parse-server path/to/configuration.json. If you're using Parse Server on Express, you may also pass these to the ParseServer object as options.

For the full list of available options, run parse-server --help or take a look at Parse Server Configurations.

Basic Options

  • appId (required) - The application id to host with this server instance. You can use any arbitrary string. For migrated apps, this should match your hosted Parse app.
  • masterKey (required) - The master key to use for overriding ACL security. You can use any arbitrary string. Keep it secret! For migrated apps, this should match your hosted Parse app.
  • databaseURI (required) - The connection string for your database, i.e. mongodb://user:pass@host.com/dbname. Be sure to URL encode your password if your password has special characters.
  • port - The default port is 1337, specify this parameter to use a different port.
  • serverURL - URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code.
  • cloud - The absolute path to your cloud code main.js file.
  • push - Configuration options for APNS and GCM push. See the Push Notifications quick start.

Client Key Options

The client keys used with Parse are no longer necessary with Parse Server. If you wish to still require them, perhaps to be able to refuse access to older clients, you can set the keys at initialization time. Setting any of these keys will require all requests to provide one of the configured keys.

  • clientKey
  • javascriptKey
  • restAPIKey
  • dotNetKey

Access Scopes

ScopeInternal dataRead-only data (1)Custom dataRestricted by CLP, ACLKey
Internalr/wr/wr/wnomaintenanceKey
Master-/-r/-r/wnomasterKey
ReadOnlyMaster-/-r/-r/-noreadOnlyMasterKey
Session-/-r/-r/wyessessionToken

(1) Parse.Object.createdAt, Parse.Object.updatedAt.

Email Verification and Password Reset

Verifying user email addresses and enabling password reset via email requires an email adapter. There are many email adapters provided and maintained by the community. The following is an example configuration with an example email adapter. See the Parse Server Options for more details and a full list of available options.

const server = ParseServer({
+  ...otherOptions,
+
+  // Enable email verification
+  verifyUserEmails: true,
+
+  // Set email verification token validity to 2 hours
+  emailVerifyTokenValidityDuration: 2 * 60 * 60,
+
+  // Set email adapter
+  emailAdapter: {
+    module: 'example-mail-adapter',
+    options: {
+      // Additional adapter options
+      ...mailAdapterOptions
+    }
+  },
+});
+

Offical email adapters maintained by Parse Platform:

Email adapters contributed by the community:

Password and Account Policy

Set a password and account policy that meets your security requirements. The following is an example configuration. See the Parse Server Options for more details and a full list of available options.

const server = ParseServer({
+  ...otherOptions,
+
+  // The account lock policy
+  accountLockout: {
+    // Lock the account for 5 minutes.
+    duration: 5,
+    // Lock an account after 3 failed log-in attempts
+    threshold: 3,
+    // Unlock the account after a successful password reset
+    unlockOnPasswordReset: true,
+  },
+
+  // The password policy
+  passwordPolicy: {
+    // Enforce a password of at least 8 characters which contain at least 1 lower case, 1 upper case and 1 digit
+    validatorPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})/,
+    // Do not allow the username as part of the password
+    doNotAllowUsername: true,
+    // Do not allow to re-use the last 5 passwords when setting a new password
+    maxPasswordHistory: 5,
+  },
+});
+

Custom Routes

Caution, this is an experimental feature that may not be appropriate for production.

Custom routes allow to build user flows with webpages, similar to the existing password reset and email verification features. Custom routes are defined with the pages option in the Parse Server configuration:

Example

const api = new ParseServer({
+  ...otherOptions,
+
+  pages: {
+    enableRouter: true, // Enables the experimental feature; required for custom routes
+    customRoutes: [{
+      method: 'GET',
+      path: 'custom_route',
+      handler: async request => {
+        // custom logic
+        // ...
+        // then, depending on the outcome, return a HTML file as response
+        return { file: 'custom_page.html' };
+      }
+    }]
+  }
+}
+

The above route can be invoked by sending a GET request to: https://[parseServerPublicUrl]/[parseMount]/[pagesEndpoint]/[appId]/[customRoute]

The handler receives the request and returns a custom_page.html webpage from the pages.pagesPath directory as response. The advantage of building a custom route this way is that it automatically makes use of Parse Server's built-in capabilities, such as page localization and dynamic placeholders.

Reserved Paths

The following paths are already used by Parse Server's built-in features and are therefore not available for custom routes. Custom routes with an identical combination of path and method are ignored.

PathHTTP MethodFeature
verify_emailGETemail verification
resend_verification_emailPOSTemail verification
choose_passwordGETpassword reset
request_password_resetGETpassword reset
request_password_resetPOSTpassword reset

Parameters

ParameterOptionalTypeDefault valueExample valuesEnvironment variableDescription
pagesyesObjectundefined-PARSE_SERVER_PAGESThe options for pages such as password reset and email verification.
pages.enableRouteryesBooleanfalse-PARSE_SERVER_PAGES_ENABLE_ROUTERIs true if the pages router should be enabled; this is required for any of the pages options to take effect. Caution, this is an experimental feature that may not be appropriate for production.
pages.customRoutesyesArray[]-PARSE_SERVER_PAGES_CUSTOM_ROUTESThe custom routes. The routes are added in the order they are defined here, which has to be considered since requests traverse routes in an ordered manner. Custom routes are traversed after build-in routes such as password reset and email verification.
pages.customRoutes.methodString-GET, POST-The HTTP method of the custom route.
pages.customRoutes.pathString-custom_page-The path of the custom route. Note that the same path can used if the method is different, for example a path custom_page can have two routes, a GET and POST route, which will be invoked depending on the HTTP request method.
pages.customRoutes.handlerAsyncFunction-async () => { ... }-The route handler that is invoked when the route matches the HTTP request. If the handler does not return a page, the request is answered with a 404 Not found. response.

Custom Pages

It’s possible to change the default pages of the app and redirect the user to another path or domain.

const server = ParseServer({
+  ...otherOptions,
+
+  customPages: {
+    passwordResetSuccess: "http://yourapp.com/passwordResetSuccess",
+    verifyEmailSuccess: "http://yourapp.com/verifyEmailSuccess",
+    parseFrameURL: "http://yourapp.com/parseFrameURL",
+    linkSendSuccess: "http://yourapp.com/linkSendSuccess",
+    linkSendFail: "http://yourapp.com/linkSendFail",
+    invalidLink: "http://yourapp.com/invalidLink",
+    invalidVerificationLink: "http://yourapp.com/invalidVerificationLink",
+    choosePassword: "http://yourapp.com/choosePassword"
+  }
+})
+

Using Environment Variables

You may configure the Parse Server using environment variables:

PORT
+PARSE_SERVER_APPLICATION_ID
+PARSE_SERVER_MASTER_KEY
+PARSE_SERVER_DATABASE_URI
+PARSE_SERVER_URL
+PARSE_SERVER_CLOUD
+

The default port is 1337, to use a different port set the PORT environment variable:

$ PORT=8080 parse-server --appId APPLICATION_ID --masterKey MASTER_KEY
+

For the full list of configurable environment variables, run parse-server --help or take a look at Parse Server Configuration.

Available Adapters

All official adapters are distributed as scoped packages on npm (@parse).

Some well maintained adapters are also available on the Parse Server Modules organization.

You can also find more adapters maintained by the community by searching on npm.

Configuring File Adapters

Parse Server allows developers to choose from several options when hosting files:

  • GridFSBucketAdapter - which is backed by MongoDB
  • S3Adapter - which is backed by Amazon S3
  • GCSAdapter - which is backed by Google Cloud Storage
  • FSAdapter - local file storage

GridFSBucketAdapter is used by default and requires no setup, but if you're interested in using Amazon S3, Google Cloud Storage, or local file storage, additional configuration information is available in the Parse Server guide.

Idempotency Enforcement

Caution, this is an experimental feature that may not be appropriate for production.

This feature deduplicates identical requests that are received by Parse Server multiple times, typically due to network issues or network adapter access restrictions on mobile operating systems.

Identical requests are identified by their request header X-Parse-Request-Id. Therefore a client request has to include this header for deduplication to be applied. Requests that do not contain this header cannot be deduplicated and are processed normally by Parse Server. This means rolling out this feature to clients is seamless as Parse Server still processes requests without this header when this feature is enabled.

This feature needs to be enabled on the client side to send the header and on the server to process the header. Refer to the specific Parse SDK docs to see whether the feature is supported yet.

Deduplication is only done for object creation and update (POST and PUT requests). Deduplication is not done for object finding and deletion (GET and DELETE requests), as these operations are already idempotent by definition.

Configuration example

let api = new ParseServer({
+    idempotencyOptions: {
+        paths: [".*"],       // enforce for all requests
+        ttl: 120             // keep request IDs for 120s
+    }
+}
+

Parameters

ParameterOptionalTypeDefault valueExample valuesEnvironment variableDescription
idempotencyOptionsyesObjectundefinedPARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONSSetting this enables idempotency enforcement for the specified paths.
idempotencyOptions.pathsyesArray<String>[].* (all paths, includes the examples below),
functions/.* (all functions),
jobs/.* (all jobs),
classes/.* (all classes),
functions/.* (all functions),
users (user creation / update),
installations (installation creation / update)
PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_PATHSAn array of path patterns that have to match the request path for request deduplication to be enabled. The mount path must not be included, for example to match the request path /parse/functions/myFunction specify the path pattern functions/myFunction. A trailing slash of the request path is ignored, for example the path pattern functions/myFunction matches both /parse/functions/myFunction and /parse/functions/myFunction/.
idempotencyOptions.ttlyesInteger30060 (60 seconds)PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_TTLThe duration in seconds after which a request record is discarded from the database. Duplicate requests due to network issues can be expected to arrive within milliseconds up to several seconds. This value must be greater than 0.

Postgres

To use this feature in Postgres, you will need to create a cron job using pgAdmin or similar to call the Postgres function idempotency_delete_expired_records() that deletes expired idempotency records. You can find an example script below. Make sure the script has the same privileges to log into Postgres as Parse Server.

#!/bin/bash
+
+set -e
+psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
+    SELECT idempotency_delete_expired_records();
+EOSQL
+
+exec "$@"
+

Assuming the script above is named, parse_idempotency_delete_expired_records.sh, a cron job that runs the script every 2 minutes may look like:

2 * * * * /root/parse_idempotency_delete_expired_records.sh >/dev/null 2>&1
+

Localization

Pages

Caution, this is an experimental feature that may not be appropriate for production.

Custom pages as well as feature pages (e.g. password reset, email verification) can be localized with the pages option in the Parse Server configuration:

const api = new ParseServer({
+  ...otherOptions,
+
+  pages: {
+    enableRouter: true, // Enables the experimental feature; required for localization
+    enableLocalization: true,
+  }
+}
+

Localization is achieved by matching a request-supplied locale parameter with localized page content. The locale can be supplied in either the request query, body or header with the following keys:

  • query: locale
  • body: locale
  • header: x-parse-page-param-locale

For example, a password reset link with the locale parameter in the query could look like this:

http://example.com/parse/apps/[appId]/request_password_reset?token=[token]&username=[username]&locale=de-AT
+
  • Localization is only available for pages in the pages directory as set with pages.pagesPath.
  • Localization for feature pages (e.g. password reset, email verification) is disabled if pages.customUrls are set, even if the custom URLs point to the pages within the pages path.
  • Only .html files are considered for localization when localizing custom pages.

Pages can be localized in two ways:

Localization with Directory Structure

Pages are localized by using the corresponding file in the directory structure where the files are placed in subdirectories named after the locale or language. The file in the base directory is the default file.

Example Directory Structure:

root/
+├── public/                  // pages base path
+│   ├── example.html         // default file
+│   └── de/                  // de language folder
+│   │   └── example.html     // de localized file
+│   └── de-AT/               // de-AT locale folder
+│   │   └── example.html     // de-AT localized file
+

Files are matched with the locale in the following order:

  1. Locale match, e.g. locale de-AT matches file in folder de-AT.
  2. Language match, e.g. locale de-CH matches file in folder de.
  3. Default; file in base folder is returned.

Configuration Example:

const api = new ParseServer({
+  ...otherOptions,
+
+  pages: {
+    enableRouter: true, // Enables the experimental feature; required for localization
+    enableLocalization: true,
+    customUrls: {
+      passwordReset: 'https://example.com/page.html'
+    }
+  }
+}
+

Pros:

  • All files are complete in their content and can be easily opened and previewed by viewing the file in a browser.

Cons:

  • In most cases, a localized page differs only slightly from the default page, which could cause a lot of duplicate code that is difficult to maintain.

Localization with JSON Resource

Pages are localized by adding placeholders in the HTML files and providing a JSON resource that contains the translations to fill into the placeholders.

Example Directory Structure:

root/
+├── public/                  // pages base path
+│   ├── example.html         // the page containing placeholders
+├── private/                 // folder outside of public scope
+│   └── translations.json    // JSON resource file
+

The JSON resource file loosely follows the i18next syntax, which is a syntax that is often supported by translation platforms, making it easy to manage translations, exporting them for use in Parse Server, and even to automate this workflow.

Example JSON Content:

{
+  "en": {               // resource for language `en` (English)
+    "translation": {
+      "greeting": "Hello!"
+    }
+  },
+  "de": {               // resource for language `de` (German)
+    "translation": {
+      "greeting": "Hallo!"
+    }
+  }
+  "de-AT": {            // resource for locale `de-AT` (Austrian German)
+    "translation": {
+      "greeting": "Servus!"
+    }
+  }
+}
+

Configuration Example:

const api = new ParseServer({
+  ...otherOptions,
+
+  pages: {
+    enableRouter: true, // Enables the experimental feature; required for localization
+    enableLocalization: true,
+    localizationJsonPath: './private/localization.json',
+    localizationFallbackLocale: 'en'
+  }
+}
+

Pros:

  • There is only one HTML file to maintain that contains the placeholders that are filled with the translations according to the locale.

Cons:

  • Files cannot be easily previewed by viewing the file in a browser because the content contains only placeholders and even HTML or CSS changes may be dynamically applied, e.g. when a localization requires a Right-To-Left layout direction.
  • Style and other fundamental layout changes may be more difficult to apply.

Dynamic placeholders

In addition to feature related default parameters such as appId and the translations provided via JSON resource, it is possible to define custom dynamic placeholders as part of the router configuration. This works independently of localization and, also if enableLocalization is disabled.

Configuration Example:

const api = new ParseServer({
+  ...otherOptions,
+
+  pages: {
+    enableRouter: true, // Enables the experimental feature; required for localization
+    placeholders: {
+      exampleKey: 'exampleValue'
+    }
+  }
+}
+

The placeholders can also be provided as function or as async function, with the locale and other feature related parameters passed through, to allow for dynamic placeholder values:

const api = new ParseServer({
+  ...otherOptions,
+
+  pages: {
+    enableRouter: true, // Enables the experimental feature; required for localization
+    placeholders: async (params) => {
+      const value = await doSomething(params.locale);
+      return {
+        exampleKey: value
+      };
+    }
+  }
+}
+

Reserved Keys

The following parameter and placeholder keys are reserved because they are used related to features such as password reset or email verification. They should not be used as translation keys in the JSON resource or as manually defined placeholder keys in the configuration: appId, appName, email, error, locale, publicServerUrl, token, username.

Parameters

ParameterOptionalTypeDefault valueExample valuesEnvironment variableDescription
pagesyesObjectundefined-PARSE_SERVER_PAGESThe options for pages such as password reset and email verification.
pages.enableRouteryesBooleanfalse-PARSE_SERVER_PAGES_ENABLE_ROUTERIs true if the pages router should be enabled; this is required for any of the pages options to take effect. Caution, this is an experimental feature that may not be appropriate for production.
pages.enableLocalizationyesBooleanfalse-PARSE_SERVER_PAGES_ENABLE_LOCALIZATIONIs true if pages should be localized; this has no effect on custom page redirects.
pages.localizationJsonPathyesStringundefined./private/translations.jsonPARSE_SERVER_PAGES_LOCALIZATION_JSON_PATHThe path to the JSON file for localization; the translations will be used to fill template placeholders according to the locale.
pages.localizationFallbackLocaleyesStringenen, en-GB, defaultPARSE_SERVER_PAGES_LOCALIZATION_FALLBACK_LOCALEThe fallback locale for localization if no matching translation is provided for the given locale. This is only relevant when providing translation resources via JSON file.
pages.placeholdersyesObject, Function, AsyncFunctionundefined{ exampleKey: 'exampleValue' }PARSE_SERVER_PAGES_PLACEHOLDERSThe placeholder keys and values which will be filled in pages; this can be a simple object or a callback function.
pages.forceRedirectyesBooleanfalse-PARSE_SERVER_PAGES_FORCE_REDIRECTIs true if responses should always be redirects and never content, false if the response type should depend on the request type (GET request -> content response; POST request -> redirect response).
pages.pagesPathyesString./public./files/pages, ../../pagesPARSE_SERVER_PAGES_PAGES_PATHThe path to the pages directory; this also defines where the static endpoint /apps points to.
pages.pagesEndpointyesStringapps-PARSE_SERVER_PAGES_PAGES_ENDPOINTThe API endpoint for the pages.
pages.customUrlsyesObject{}{ passwordReset: 'https://example.com/page.html' }PARSE_SERVER_PAGES_CUSTOM_URLSThe URLs to the custom pages
pages.customUrls.passwordResetyesStringpassword_reset.html-PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESETThe URL to the custom page for password reset.
pages.customUrls.passwordResetSuccessyesStringpassword_reset_success.html-PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET_SUCCESSThe URL to the custom page for password reset -> success.
pages.customUrls.passwordResetLinkInvalidyesStringpassword_reset_link_invalid.html-PARSE_SERVER_PAGES_CUSTOM_URL_PASSWORD_RESET_LINK_INVALIDThe URL to the custom page for password reset -> link invalid.
pages.customUrls.emailVerificationSuccessyesStringemail_verification_success.html-PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SUCCESSThe URL to the custom page for email verification -> success.
pages.customUrls.emailVerificationSendFailyesStringemail_verification_send_fail.html-PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_FAILThe URL to the custom page for email verification -> link send fail.
pages.customUrls.emailVerificationSendSuccessyesStringemail_verification_send_success.html-PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_SEND_SUCCESSThe URL to the custom page for email verification -> resend link -> success.
pages.customUrls.emailVerificationLinkInvalidyesStringemail_verification_link_invalid.html-PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_INVALIDThe URL to the custom page for email verification -> link invalid.
pages.customUrls.emailVerificationLinkExpiredyesStringemail_verification_link_expired.html-PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_EXPIREDThe URL to the custom page for email verification -> link expired.

Notes

  • In combination with the Parse Server API Mail Adapter Parse Server provides a fully localized flow (emails -> pages) for the user. The email adapter sends a localized email and adds a locale parameter to the password reset or email verification link, which is then used to respond with localized pages.

Logging

Parse Server will, by default, log:

  • to the console
  • daily rotating files as new line delimited JSON

Logs are also viewable in Parse Dashboard.

Want to log each request and response? Set the VERBOSE environment variable when starting parse-server. Usage :- VERBOSE='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY

Want logs to be placed in a different folder? Pass the PARSE_SERVER_LOGS_FOLDER environment variable when starting parse-server. Usage :- PARSE_SERVER_LOGS_FOLDER='<path-to-logs-folder>' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY

Want to log specific levels? Pass the logLevel parameter when starting parse-server. Usage :- parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --logLevel LOG_LEVEL

Want new line delimited JSON error logs (for consumption by CloudWatch, Google Cloud Logging, etc)? Pass the JSON_LOGS environment variable when starting parse-server. Usage :- JSON_LOGS='1' parse-server --appId APPLICATION_ID --masterKey MASTER_KEY

Deprecations

See the Deprecation Plan for an overview of deprecations and planned breaking changes.

Live Query

Live queries are meant to be used in real-time reactive applications, where just using the traditional query paradigm could cause several problems, like increased response time and high network and server usage. Live queries should be used in cases where you need to continuously update a page with fresh data coming from the database, which often happens in (but is not limited to) online games, messaging clients and shared to-do lists.

Take a look at Live Query Guide, Live Query Server Setup Guide and Live Query Protocol Specification. You can setup a standalone server or multiple instances for scalability (recommended).

GraphQL

GraphQL, developed by Facebook, is an open-source data query and manipulation language for APIs. In addition to the traditional REST API, Parse Server automatically generates a GraphQL API based on your current application schema. Parse Server also allows you to define your custom GraphQL queries and mutations, whose resolvers can be bound to your cloud code functions.

Running

Using the CLI

The easiest way to run the Parse GraphQL API is through the CLI:

$ npm install -g parse-server mongodb-runner
+$ mongodb-runner start
+$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --publicServerURL http://localhost:1337/parse --mountGraphQL --mountPlayground
+

After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.

Note: Do NOT use --mountPlayground option in production. Parse Dashboard has a built-in GraphQL Playground and it is the recommended option for production apps.

Using Docker

You can also run the Parse GraphQL API inside a Docker container:

$ git clone https://github.com/parse-community/parse-server
+$ cd parse-server
+$ docker build --tag parse-server .
+$ docker run --name my-mongo -d mongo
+

Running the Parse Server Image

$ docker run --name my-parse-server --link my-mongo:mongo -v config-vol:/parse-server/config -p 1337:1337 -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test --publicServerURL http://localhost:1337/parse --mountGraphQL --mountPlayground
+

Note: If you want to use Cloud Code, add -v cloud-code-vol:/parse-server/cloud --cloud /parse-server/cloud/main.js to the command above. Make sure main.js is in the cloud-code-vol directory before starting Parse Server.

After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.

Note: Do NOT use --mountPlayground option in production. Parse Dashboard has a built-in GraphQL Playground and it is the recommended option for production apps.

Using Express.js

You can also mount the GraphQL API in an Express.js application together with the REST API or solo. You first need to create a new project and install the required dependencies:

$ mkdir my-app
+$ cd my-app
+$ npm install parse-server express --save
+

Then, create an index.js file with the following content:

const express = require('express');
+const { ParseServer, ParseGraphQLServer } = require('parse-server');
+
+const app = express();
+
+const parseServer = new ParseServer({
+  databaseURI: 'mongodb://localhost:27017/test',
+  appId: 'APPLICATION_ID',
+  masterKey: 'MASTER_KEY',
+  serverURL: 'http://localhost:1337/parse',
+  publicServerURL: 'http://localhost:1337/parse'
+});
+
+const parseGraphQLServer = new ParseGraphQLServer(
+  parseServer,
+  {
+    graphQLPath: '/graphql',
+    playgroundPath: '/playground'
+  }
+);
+
+app.use('/parse', parseServer.app); // (Optional) Mounts the REST API
+parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API
+parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production
+
+await parseServer.start();
+app.listen(1337, function() {
+  console.log('REST API running on http://localhost:1337/parse');
+  console.log('GraphQL API running on http://localhost:1337/graphql');
+  console.log('GraphQL Playground running on http://localhost:1337/playground');
+});
+

And finally start your app:

$ npx mongodb-runner start
+$ node index.js
+

After starting the app, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.

Note: Do NOT mount the GraphQL Playground in production. Parse Dashboard has a built-in GraphQL Playground and it is the recommended option for production apps.

Checking the API health

Run the following:

query Health {
+  health
+}
+

You should receive the following response:

{
+  "data": {
+    "health": true
+  }
+}
+

Creating your first class

Since your application does not have any schema yet, you can use the createClass mutation to create your first class. Run the following:

mutation CreateClass {
+  createClass(
+    name: "GameScore"
+    schemaFields: {
+      addStrings: [{ name: "playerName" }]
+      addNumbers: [{ name: "score" }]
+      addBooleans: [{ name: "cheatMode" }]
+    }
+  ) {
+    name
+    schemaFields {
+      name
+      __typename
+    }
+  }
+}
+

You should receive the following response:

{
+  "data": {
+    "createClass": {
+      "name": "GameScore",
+      "schemaFields": [
+        {
+          "name": "objectId",
+          "__typename": "SchemaStringField"
+        },
+        {
+          "name": "updatedAt",
+          "__typename": "SchemaDateField"
+        },
+        {
+          "name": "createdAt",
+          "__typename": "SchemaDateField"
+        },
+        {
+          "name": "playerName",
+          "__typename": "SchemaStringField"
+        },
+        {
+          "name": "score",
+          "__typename": "SchemaNumberField"
+        },
+        {
+          "name": "cheatMode",
+          "__typename": "SchemaBooleanField"
+        },
+        {
+          "name": "ACL",
+          "__typename": "SchemaACLField"
+        }
+      ]
+    }
+  }
+}
+

Using automatically generated operations

Parse Server learned from the first class that you created and now you have the GameScore class in your schema. You can now start using the automatically generated operations!

Run the following to create your first object:

mutation CreateGameScore {
+  createGameScore(
+    fields: {
+      playerName: "Sean Plott"
+      score: 1337
+      cheatMode: false
+    }
+  ) {
+    id
+    updatedAt
+    createdAt
+    playerName
+    score
+    cheatMode
+    ACL
+  }
+}
+

You should receive a response similar to this:

{
+  "data": {
+    "createGameScore": {
+      "id": "XN75D94OBD",
+      "updatedAt": "2019-09-17T06:50:26.357Z",
+      "createdAt": "2019-09-17T06:50:26.357Z",
+      "playerName": "Sean Plott",
+      "score": 1337,
+      "cheatMode": false,
+      "ACL": null
+    }
+  }
+}
+

You can also run a query to this new class:

query GameScores {
+  gameScores {
+    results {
+      id
+      updatedAt
+      createdAt
+      playerName
+      score
+      cheatMode
+      ACL
+    }
+  }
+}
+

You should receive a response similar to this:

{
+  "data": {
+    "gameScores": {
+      "results": [
+        {
+          "id": "XN75D94OBD",
+          "updatedAt": "2019-09-17T06:50:26.357Z",
+          "createdAt": "2019-09-17T06:50:26.357Z",
+          "playerName": "Sean Plott",
+          "score": 1337,
+          "cheatMode": false,
+          "ACL": null
+        }
+      ]
+    }
+  }
+}
+

Customizing your GraphQL Schema

Parse GraphQL Server allows you to create a custom GraphQL schema with own queries and mutations to be merged with the auto-generated ones. You can resolve these operations using your regular cloud code functions.

To start creating your custom schema, you need to code a schema.graphql file and initialize Parse Server with --graphQLSchema and --cloud options:

$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --publicServerURL http://localhost:1337/parse --cloud ./cloud/main.js --graphQLSchema ./cloud/schema.graphql --mountGraphQL --mountPlayground
+

Creating your first custom query

Use the code below for your schema.graphql and main.js files. Then restart your Parse Server.

# schema.graphql
+extend type Query {
+  hello: String! @resolve
+}
+
// main.js
+Parse.Cloud.define('hello', async () => {
+  return 'Hello world!';
+});
+

You can now run your custom query using GraphQL Playground:

query {
+  hello
+}
+

You should receive the response below:

{
+  "data": {
+    "hello": "Hello world!"
+  }
+}
+

Learning more

The Parse GraphQL Guide is a very good source for learning how to use the Parse GraphQL API.

You also have a very powerful tool inside your GraphQL Playground. Please look at the right side of your GraphQL Playground. You will see the DOCS and SCHEMA menus. They are automatically generated by analyzing your application schema. Please refer to them and learn more about everything that you can do with your Parse GraphQL API.

Additionally, the GraphQL Learn Section is a very good source to learn more about the power of the GraphQL language.

Contributing

Please see the Contributing Guide.

Contributors

This project exists thanks to all the people who contribute... we'd love to see your face on this list!

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. Become a sponsor!

Backers

Support us with a monthly donation and help us continue our activities. Become a backer!

\ No newline at end of file diff --git a/api/7.2.0/module-AdapterLoader.html b/api/7.2.0/module-AdapterLoader.html new file mode 100644 index 0000000000..5fe2219b3f --- /dev/null +++ b/api/7.2.0/module-AdapterLoader.html @@ -0,0 +1,3 @@ +Module: AdapterLoader
On this page

Methods

(static) loadAdapter(adapter, defaultAdapter, options) → {Object}

Parameters:
NameTypeDescription
adapterAdapter

an adapter

defaultAdapterAdapter

the default adapter to load

optionsany

options to pass to the contstructor

Returns:

the loaded adapter

Type: 
Object
\ No newline at end of file diff --git a/api/7.2.0/module-Adapters%0ALogger%20Adapter%0AAllows%20you%20to%20change%20the%20logger%20mechanism%0ADefault%20is%20WinstonLoggerAdapter.js.LoggerAdapter.html b/api/7.2.0/module-Adapters%0ALogger%20Adapter%0AAllows%20you%20to%20change%20the%20logger%20mechanism%0ADefault%20is%20WinstonLoggerAdapter.js.LoggerAdapter.html new file mode 100644 index 0000000000..350115dd80 --- /dev/null +++ b/api/7.2.0/module-Adapters%0ALogger%20Adapter%0AAllows%20you%20to%20change%20the%20logger%20mechanism%0ADefault%20is%20WinstonLoggerAdapter.js.LoggerAdapter.html @@ -0,0 +1,3 @@ +Interface: LoggerAdapter
On this page

LoggerAdapter

Methods

log(level, message, metadata)

log

Parameters:
NameTypeDescription
levelString
messageString
metadataObject
\ No newline at end of file diff --git a/api/7.2.0/module-Adapters%0AMail%20Adapter%20prototype%0AA%20MailAdapter%20should%20implement%20at%20least%20sendMail().MailAdapter.html b/api/7.2.0/module-Adapters%0AMail%20Adapter%20prototype%0AA%20MailAdapter%20should%20implement%20at%20least%20sendMail().MailAdapter.html new file mode 100644 index 0000000000..1929f98ac8 --- /dev/null +++ b/api/7.2.0/module-Adapters%0AMail%20Adapter%20prototype%0AA%20MailAdapter%20should%20implement%20at%20least%20sendMail().MailAdapter.html @@ -0,0 +1,3 @@ +Interface: MailAdapter
On this page

MailAdapter

Methods

sendMail(options)

A method for sending mail

Parameters:
NameTypeDescription
options

would have the parameters

  • to: the recipient
  • text: the raw text of the message
  • subject: the subject of the email
\ No newline at end of file diff --git a/api/7.2.0/module-Adapters.CacheAdapter.html b/api/7.2.0/module-Adapters.CacheAdapter.html new file mode 100644 index 0000000000..fa88fca58d --- /dev/null +++ b/api/7.2.0/module-Adapters.CacheAdapter.html @@ -0,0 +1,3 @@ +Interface: CacheAdapter
On this page

Adapters. CacheAdapter

Methods

clear()

Empty a cache

del(key)

Remove a value from the cache.

Parameters:
NameTypeDescription
keyString

Cache key to remove

get(key) → {Promise}

Get a value in the cache

Parameters:
NameTypeDescription
keyString

Cache key to get

Returns:

that will eventually resolve to the value in the cache.

Type: 
Promise

put(key, value, ttl)

Set a value in the cache

Parameters:
NameTypeDescription
keyString

Cache key to set

valueString

Value to set the key

ttlString

Optional TTL

\ No newline at end of file diff --git a/api/7.2.0/module-Adapters.FilesAdapter.html b/api/7.2.0/module-Adapters.FilesAdapter.html new file mode 100644 index 0000000000..6b1545af66 --- /dev/null +++ b/api/7.2.0/module-Adapters.FilesAdapter.html @@ -0,0 +1,3 @@ +Interface: FilesAdapter
On this page

Adapters. FilesAdapter

Methods

createFile(filename, data, contentType, options) → {Promise}

Responsible for storing the file in order to be retrieved later by its filename

Parameters:
NameTypeDescription
filenamestring

the filename to save

data*

the buffer of data from the file

contentTypestring

the supposed contentType

optionsobject

(Optional) options to be passed to file adapter (S3 File Adapter Only)

  • tags: object containing key value pairs that will be stored with file
  • metadata: object containing key value pairs that will be sotred with file (https://docs.aws.amazon.com/AmazonS3/latest/user-guide/add-object-metadata.html)
Returns:

a promise that should fail if the storage didn't succeed

Type: 
Promise

deleteFile(filename) → {Promise}

Responsible for deleting the specified file

Parameters:
NameTypeDescription
filenamestring

the filename to delete

Returns:

a promise that should fail if the deletion didn't succeed

Type: 
Promise

getFileData(filename) → {Promise}

Responsible for retrieving the data of the specified file

Parameters:
NameTypeDescription
filenamestring

the name of file to retrieve

Returns:

a promise that should pass with the file data or fail on error

Type: 
Promise

getFileLocation(config, filename) → {string}

Returns an absolute URL where the file can be accessed

Parameters:
NameTypeDescription
configConfig

server configuration

filenamestring
Returns:

Absolute URL

Type: 
string
\ No newline at end of file diff --git a/api/7.2.0/module-Adapters.PubSubAdapter.html b/api/7.2.0/module-Adapters.PubSubAdapter.html new file mode 100644 index 0000000000..84f8cf6c03 --- /dev/null +++ b/api/7.2.0/module-Adapters.PubSubAdapter.html @@ -0,0 +1,3 @@ +Interface: PubSubAdapter
On this page

Adapters. PubSubAdapter

\ No newline at end of file diff --git a/api/7.2.0/module-Adapters.PushAdapter.html b/api/7.2.0/module-Adapters.PushAdapter.html new file mode 100644 index 0000000000..4a4f29cb8c --- /dev/null +++ b/api/7.2.0/module-Adapters.PushAdapter.html @@ -0,0 +1,3 @@ +Interface: PushAdapter
On this page

Adapters. PushAdapter

Methods

getValidPushTypes() → {Array}

Get an array of valid push types.

Returns:

An array of valid push types

Type: 
Array

send(body, installations, pushStatus) → {Promise}

Parameters:
NameTypeDescription
bodyany
installationsArray.<Parse.Installation>
pushStatusany
Returns:
Type: 
Promise
\ No newline at end of file diff --git a/api/7.2.0/module-Adapters.WSSAdapter.html b/api/7.2.0/module-Adapters.WSSAdapter.html new file mode 100644 index 0000000000..5f4ccfea5c --- /dev/null +++ b/api/7.2.0/module-Adapters.WSSAdapter.html @@ -0,0 +1,3 @@ +Interface: WSSAdapter
On this page

Adapters. WSSAdapter

Methods

close()

Closes server.

start(options)

Initialize Connection.

Parameters:
NameTypeDescription
optionsObject
\ No newline at end of file diff --git a/api/7.2.0/module-Adapters.html b/api/7.2.0/module-Adapters.html new file mode 100644 index 0000000000..5279d6a42a --- /dev/null +++ b/api/7.2.0/module-Adapters.html @@ -0,0 +1,3 @@ +Module: Adapters
On this page
\ No newline at end of file diff --git a/api/7.2.0/scripts/core.js b/api/7.2.0/scripts/core.js new file mode 100644 index 0000000000..238a88294a --- /dev/null +++ b/api/7.2.0/scripts/core.js @@ -0,0 +1,726 @@ +/* global document */ +var accordionLocalStorageKey = 'accordion-id'; +var themeLocalStorageKey = 'theme'; +var fontSizeLocalStorageKey = 'font-size'; +var html = document.querySelector('html'); + +var MAX_FONT_SIZE = 30; +var MIN_FONT_SIZE = 10; + +// eslint-disable-next-line no-undef +var localStorage = window.localStorage; + +function getTheme() { + var theme = localStorage.getItem(themeLocalStorageKey); + + if (theme) return theme; + + theme = document.body.getAttribute('data-theme'); + + switch (theme) { + case 'dark': + case 'light': + return theme; + case 'fallback-dark': + if ( + // eslint-disable-next-line no-undef + window.matchMedia('(prefers-color-scheme)').matches && + // eslint-disable-next-line no-undef + window.matchMedia('(prefers-color-scheme: light)').matches + ) { + return 'light'; + } + + return 'dark'; + + case 'fallback-light': + if ( + // eslint-disable-next-line no-undef + window.matchMedia('(prefers-color-scheme)').matches && + // eslint-disable-next-line no-undef + window.matchMedia('(prefers-color-scheme: dark)').matches + ) { + return 'dark'; + } + + return 'light'; + + default: + return 'dark'; + } +} + +function localUpdateTheme(theme) { + var body = document.body; + var svgUse = document.querySelectorAll('.theme-svg-use'); + var iconID = theme === 'dark' ? '#light-theme-icon' : '#dark-theme-icon'; + + body.setAttribute('data-theme', theme); + body.classList.remove('dark', 'light'); + body.classList.add(theme); + + svgUse.forEach(function (svg) { + svg.setAttribute('xlink:href', iconID); + }); +} + +function updateTheme(theme) { + localUpdateTheme(theme); + localStorage.setItem(themeLocalStorageKey, theme); +} + +function toggleTheme() { + var body = document.body; + var theme = body.getAttribute('data-theme'); + + var newTheme = theme === 'dark' ? 'light' : 'dark'; + + updateTheme(newTheme); +} + +(function () { + var theme = getTheme(); + + updateTheme(theme); +})(); + +/** + * Function to set accordion id to localStorage. + * @param {string} id Accordion id + */ +function setAccordionIdToLocalStorage(id) { + /** + * @type {object} + */ + var ids = JSON.parse(localStorage.getItem(accordionLocalStorageKey)); + + ids[id] = id; + localStorage.setItem(accordionLocalStorageKey, JSON.stringify(ids)); +} + +/** + * Function to remove accordion id from localStorage. + * @param {string} id Accordion id + */ +function removeAccordionIdFromLocalStorage(id) { + /** + * @type {object} + */ + var ids = JSON.parse(localStorage.getItem(accordionLocalStorageKey)); + + delete ids[id]; + localStorage.setItem(accordionLocalStorageKey, JSON.stringify(ids)); +} + +/** + * Function to get all accordion ids from localStorage. + * + * @returns {object} + */ +function getAccordionIdsFromLocalStorage() { + /** + * @type {object} + */ + var ids = JSON.parse(localStorage.getItem(accordionLocalStorageKey)); + + return ids || {}; +} + +function toggleAccordion(element) { + var currentNode = element; + var isCollapsed = currentNode.getAttribute('data-isopen') === 'false'; + + if (isCollapsed) { + currentNode.setAttribute('data-isopen', 'true'); + setAccordionIdToLocalStorage(currentNode.id); + } else { + currentNode.setAttribute('data-isopen', 'false'); + removeAccordionIdFromLocalStorage(currentNode.id); + } +} + +function initAccordion() { + if ( + localStorage.getItem(accordionLocalStorageKey) === undefined || + localStorage.getItem(accordionLocalStorageKey) === null + ) { + localStorage.setItem(accordionLocalStorageKey, '{}'); + } + var allAccordion = document.querySelectorAll('.sidebar-section-title'); + var ids = getAccordionIdsFromLocalStorage(); + + allAccordion.forEach(function (item) { + item.addEventListener('click', function () { + toggleAccordion(item); + }); + if (item.id in ids) { + toggleAccordion(item); + } + }); +} + +function isSourcePage() { + return Boolean(document.querySelector('#source-page')); +} + +function bringElementIntoView(element, updateHistory = true) { + // If element is null then we are not going further + if (!element) { + return; + } + + /** + * tocbotInstance is defined in layout.tmpl + * It is defined when we are initializing tocbot. + * + */ + // eslint-disable-next-line no-undef + if (tocbotInstance) { + setTimeout( + // eslint-disable-next-line no-undef + () => tocbotInstance.updateTocListActiveElement(element), + 60 + ); + } + var navbar = document.querySelector('.navbar-container'); + var body = document.querySelector('.main-content'); + var elementTop = element.getBoundingClientRect().top; + + var offset = 16; + + if (navbar) { + offset += navbar.scrollHeight; + } + + if (body) { + body.scrollBy(0, elementTop - offset); + } + + if (updateHistory) { + // eslint-disable-next-line no-undef + history.pushState(null, null, '#' + element.id); + } +} + +// eslint-disable-next-line no-unused-vars +function bringLinkToView(event) { + event.preventDefault(); + event.stopPropagation(); + var id = event.currentTarget.getAttribute('href'); + + if (!id) { + return; + } + + var element = document.getElementById(id.slice(1)); + + if (element) { + bringElementIntoView(element); + } +} + +function bringIdToViewOnMount() { + if (isSourcePage()) { + return; + } + + // eslint-disable-next-line no-undef + var id = window.location.hash; + + if (id === '') { + return; + } + + var element = document.getElementById(id.slice(1)); + + if (!element) { + id = decodeURI(id); + element = document.getElementById(id.slice(1)); + } + + if (element) { + bringElementIntoView(element, false); + } +} + +function createAnchorElement(id) { + var anchor = document.createElement('a'); + + anchor.textContent = '#'; + anchor.href = '#' + id; + anchor.classList.add('link-anchor'); + anchor.onclick = bringLinkToView; + + return anchor; +} + +function addAnchor() { + var main = document.querySelector('.main-content').querySelector('section'); + + var h1 = main.querySelectorAll('h1'); + var h2 = main.querySelectorAll('h2'); + var h3 = main.querySelectorAll('h3'); + var h4 = main.querySelectorAll('h4'); + + var targets = [h1, h2, h3, h4]; + + targets.forEach(function (target) { + target.forEach(function (heading) { + var anchor = createAnchorElement(heading.id); + + heading.classList.add('has-anchor'); + heading.append(anchor); + }); + }); +} + +/** + * + * @param {string} value + */ +function copy(value) { + const el = document.createElement('textarea'); + + el.value = value; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); +} + +function showTooltip(id) { + var tooltip = document.getElementById(id); + + tooltip.classList.add('show-tooltip'); + setTimeout(function () { + tooltip.classList.remove('show-tooltip'); + }, 3000); +} + +/* eslint-disable-next-line */ +function copyFunction(id) { + // selecting the pre element + var code = document.getElementById(id); + + // selecting the ol.linenums + var element = code.querySelector('.linenums'); + + if (!element) { + // selecting the code block + element = code.querySelector('code'); + } + + // copy + copy(element.innerText.trim().replace(/(^\t)/gm, '')); + + // show tooltip + showTooltip('tooltip-' + id); +} + +function hideTocOnSourcePage() { + if (isSourcePage()) { + document.querySelector('.toc-container').style.display = 'none'; + } +} + +function getPreTopBar(id, lang = '') { + // tooltip + var tooltip = '
Copied!
'; + + // template of copy to clipboard icon container + var copyToClipboard = + ''; + + var langNameDiv = + '
' + + lang.toLocaleUpperCase() + + '
'; + + var topBar = + '
' + + langNameDiv + + copyToClipboard + + '
'; + + return topBar; +} + +function getPreDiv() { + var divElement = document.createElement('div'); + + divElement.classList.add('pre-div'); + + return divElement; +} + +function processAllPre() { + var targets = document.querySelectorAll('pre'); + var footer = document.querySelector('#PeOAagUepe'); + var navbar = document.querySelector('#VuAckcnZhf'); + + var navbarHeight = 0; + var footerHeight = 0; + + if (footer) { + footerHeight = footer.getBoundingClientRect().height; + } + + if (navbar) { + navbarHeight = navbar.getBoundingClientRect().height; + } + + // eslint-disable-next-line no-undef + var preMaxHeight = window.innerHeight - navbarHeight - footerHeight - 250; + + targets.forEach(function (pre, idx) { + var parent = pre.parentNode; + + if (parent && parent.getAttribute('data-skip-pre-process') === 'true') { + return; + } + + var div = getPreDiv(); + var id = 'ScDloZOMdL' + idx; + + var lang = pre.getAttribute('data-lang') || 'code'; + var topBar = getPreTopBar(id, lang); + + div.innerHTML = topBar; + + pre.style.maxHeight = preMaxHeight + 'px'; + pre.id = id; + pre.classList.add('prettyprint'); + pre.parentNode.insertBefore(div, pre); + div.appendChild(pre); + }); +} + +function highlightAndBringLineIntoView() { + // eslint-disable-next-line no-undef + var lineNumber = window.location.hash.replace('#line', ''); + + try { + var selector = '[data-line-number="' + lineNumber + '"'; + + var element = document.querySelector(selector); + + element.scrollIntoView(); + element.parentNode.classList.add('selected'); + } catch (error) { + console.error(error); + } +} + +function getFontSize() { + var currentFontSize = 16; + + try { + currentFontSize = Number.parseInt( + html.style.fontSize.split('px')[0], + 10 + ); + } catch (error) { + console.log(error); + } + + return currentFontSize; +} + +function localUpdateFontSize(fontSize) { + html.style.fontSize = fontSize + 'px'; + + var fontSizeText = document.querySelector( + '#b77a68a492f343baabea06fad81f651e' + ); + + if (fontSizeText) { + fontSizeText.innerHTML = fontSize; + } +} + +function updateFontSize(fontSize) { + localUpdateFontSize(fontSize); + localStorage.setItem(fontSizeLocalStorageKey, fontSize); +} + +(function () { + var fontSize = getFontSize(); + var fontSizeInLocalStorage = localStorage.getItem(fontSizeLocalStorageKey); + + if (fontSizeInLocalStorage) { + var n = Number.parseInt(fontSizeInLocalStorage, 10); + + if (n === fontSize) { + return; + } + updateFontSize(n); + } else { + updateFontSize(fontSize); + } +})(); + +// eslint-disable-next-line no-unused-vars +function incrementFont(event) { + var n = getFontSize(); + + if (n < MAX_FONT_SIZE) { + updateFontSize(n + 1); + } +} + +// eslint-disable-next-line no-unused-vars +function decrementFont(event) { + var n = getFontSize(); + + if (n > MIN_FONT_SIZE) { + updateFontSize(n - 1); + } +} + +function fontSizeTooltip() { + var fontSize = getFontSize(); + + return ` +
+ +
+ ${fontSize} +
+ + +
+ + `; +} + +function initTooltip() { + // add tooltip to navbar item + // eslint-disable-next-line no-undef + tippy('.theme-toggle', { + content: 'Toggle Theme', + delay: 500, + }); + + // eslint-disable-next-line no-undef + tippy('.search-button', { + content: 'Search', + delay: 500, + }); + + // eslint-disable-next-line no-undef + tippy('.font-size', { + content: 'Change font size', + delay: 500, + }); + + // eslint-disable-next-line no-undef + tippy('.codepen-button', { + content: 'Open code in CodePen', + placement: 'left', + }); + + // eslint-disable-next-line no-undef + tippy('.copy-code', { + content: 'Copy this code', + placement: 'left', + }); + + // eslint-disable-next-line no-undef + tippy('.font-size', { + content: fontSizeTooltip(), + trigger: 'click', + interactive: true, + allowHTML: true, + placement: 'left', + }); +} + +function fixTable() { + const tables = document.querySelectorAll('table'); + + for (const table of tables) { + if (table.classList.contains('hljs-ln')) { + // don't want to wrap code blocks. + return; + } + + var div = document.createElement('div'); + + div.classList.add('table-div'); + table.parentNode.insertBefore(div, table); + div.appendChild(table); + } +} + +function hideMobileMenu() { + var mobileMenuContainer = document.querySelector('#mobile-sidebar'); + var target = document.querySelector('#mobile-menu'); + var svgUse = target.querySelector('use'); + + if (mobileMenuContainer) { + mobileMenuContainer.classList.remove('show'); + } + if (target) { + target.setAttribute('data-isopen', 'false'); + } + if (svgUse) { + svgUse.setAttribute('xlink:href', '#menu-icon'); + } +} + +function showMobileMenu() { + var mobileMenuContainer = document.querySelector('#mobile-sidebar'); + var target = document.querySelector('#mobile-menu'); + var svgUse = target.querySelector('use'); + + if (mobileMenuContainer) { + mobileMenuContainer.classList.add('show'); + } + if (target) { + target.setAttribute('data-isopen', 'true'); + } + if (svgUse) { + svgUse.setAttribute('xlink:href', '#close-icon'); + } +} + +function onMobileMenuClick() { + var target = document.querySelector('#mobile-menu'); + var isOpen = target.getAttribute('data-isopen') === 'true'; + + if (isOpen) { + hideMobileMenu(); + } else { + showMobileMenu(); + } +} + +function initMobileMenu() { + var menu = document.querySelector('#mobile-menu'); + + if (menu) { + menu.addEventListener('click', onMobileMenuClick); + } +} + +function addHrefToSidebarTitle() { + var titles = document.querySelectorAll('.sidebar-title-anchor'); + + titles.forEach(function (title) { + // eslint-disable-next-line no-undef + title.setAttribute('href', baseURL); + }); +} + +function highlightActiveLinkInSidebar() { + const list = document.location.href.split('/'); + const targetURL = list[list.length - 1]; + let element = document.querySelector(`.sidebar a[href*='${targetURL}']`); + + if (!element) { + try { + element = document.querySelector( + `.sidebar a[href*='${targetURL.split('#')[0]}']` + ); + } catch (e) { + console.error(e); + + return; + } + } + + if (!element) return; + + element.parentElement.classList.add('active'); + element.scrollIntoView(); +} + +function onDomContentLoaded() { + var themeButton = document.querySelectorAll('.theme-toggle'); + + initMobileMenu(); + + if (themeButton) { + themeButton.forEach(function (button) { + button.addEventListener('click', toggleTheme); + }); + } + + // Highlighting code + + // eslint-disable-next-line no-undef + hljs.addPlugin({ + 'after:highlightElement': function (obj) { + // Replace 'code' with result.language when + // we are able to cross-check the correctness of + // result. + obj.el.parentNode.setAttribute('data-lang', 'code'); + }, + }); + // eslint-disable-next-line no-undef + hljs.highlightAll(); + // eslint-disable-next-line no-undef + hljs.initLineNumbersOnLoad({ + singleLine: true, + }); + + // Highlight complete + + initAccordion(); + addAnchor(); + processAllPre(); + hideTocOnSourcePage(); + setTimeout(function () { + bringIdToViewOnMount(); + if (isSourcePage()) { + highlightAndBringLineIntoView(); + } + }, 1000); + initTooltip(); + fixTable(); + addHrefToSidebarTitle(); + highlightActiveLinkInSidebar(); +} + +// eslint-disable-next-line no-undef +window.addEventListener('DOMContentLoaded', onDomContentLoaded); + +// eslint-disable-next-line no-undef +window.addEventListener('hashchange', (event) => { + const url = new URL(event.newURL); + + if (url.hash !== '') { + bringIdToViewOnMount(url.hash); + } +}); + +// eslint-disable-next-line no-undef +window.addEventListener('storage', (event) => { + if (event.newValue === 'undefined') return; + + initTooltip(); + + if (event.key === themeLocalStorageKey) localUpdateTheme(event.newValue); + if (event.key === fontSizeLocalStorageKey) + localUpdateFontSize(event.newValue); +}); diff --git a/api/7.2.0/scripts/core.min.js b/api/7.2.0/scripts/core.min.js new file mode 100644 index 0000000000..76e237d8c0 --- /dev/null +++ b/api/7.2.0/scripts/core.min.js @@ -0,0 +1,23 @@ +var accordionLocalStorageKey="accordion-id",themeLocalStorageKey="theme",fontSizeLocalStorageKey="font-size",html=document.querySelector("html"),MAX_FONT_SIZE=30,MIN_FONT_SIZE=10,localStorage=window.localStorage;function getTheme(){var e=localStorage.getItem(themeLocalStorageKey);if(e)return e;switch(e=document.body.getAttribute("data-theme")){case"dark":case"light":return e;case"fallback-dark":return window.matchMedia("(prefers-color-scheme)").matches&&window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";case"fallback-light":return window.matchMedia("(prefers-color-scheme)").matches&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";default:return"dark"}}function localUpdateTheme(e){var t=document.body,o=document.querySelectorAll(".theme-svg-use"),n="dark"===e?"#light-theme-icon":"#dark-theme-icon";t.setAttribute("data-theme",e),t.classList.remove("dark","light"),t.classList.add(e),o.forEach(function(e){e.setAttribute("xlink:href",n)})}function updateTheme(e){localUpdateTheme(e),localStorage.setItem(themeLocalStorageKey,e)}function toggleTheme(){updateTheme("dark"===document.body.getAttribute("data-theme")?"light":"dark")}function setAccordionIdToLocalStorage(e){var t=JSON.parse(localStorage.getItem(accordionLocalStorageKey));t[e]=e,localStorage.setItem(accordionLocalStorageKey,JSON.stringify(t))}function removeAccordionIdFromLocalStorage(e){var t=JSON.parse(localStorage.getItem(accordionLocalStorageKey));delete t[e],localStorage.setItem(accordionLocalStorageKey,JSON.stringify(t))}function getAccordionIdsFromLocalStorage(){return JSON.parse(localStorage.getItem(accordionLocalStorageKey))||{}}function toggleAccordion(e){"false"===e.getAttribute("data-isopen")?(e.setAttribute("data-isopen","true"),setAccordionIdToLocalStorage(e.id)):(e.setAttribute("data-isopen","false"),removeAccordionIdFromLocalStorage(e.id))}function initAccordion(){void 0!==localStorage.getItem(accordionLocalStorageKey)&&null!==localStorage.getItem(accordionLocalStorageKey)||localStorage.setItem(accordionLocalStorageKey,"{}");var e=document.querySelectorAll(".sidebar-section-title"),t=getAccordionIdsFromLocalStorage();e.forEach(function(e){e.addEventListener("click",function(){toggleAccordion(e)}),e.id in t&&toggleAccordion(e)})}function isSourcePage(){return Boolean(document.querySelector("#source-page"))}function bringElementIntoView(e,t=!0){var o,n,i,c;e&&(tocbotInstance&&setTimeout(()=>tocbotInstance.updateTocListActiveElement(e),60),o=document.querySelector(".navbar-container"),n=document.querySelector(".main-content"),i=e.getBoundingClientRect().top,c=16,o&&(c+=o.scrollHeight),n&&n.scrollBy(0,i-c),t&&history.pushState(null,null,"#"+e.id))}function bringLinkToView(e){e.preventDefault(),e.stopPropagation();var e=e.currentTarget.getAttribute("href");!e||(e=document.getElementById(e.slice(1)))&&bringElementIntoView(e)}function bringIdToViewOnMount(){var e,t;isSourcePage()||""!==(e=window.location.hash)&&((t=document.getElementById(e.slice(1)))||(e=decodeURI(e),t=document.getElementById(e.slice(1))),t&&bringElementIntoView(t,!1))}function createAnchorElement(e){var t=document.createElement("a");return t.textContent="#",t.href="#"+e,t.classList.add("link-anchor"),t.onclick=bringLinkToView,t}function addAnchor(){var e=document.querySelector(".main-content").querySelector("section");[e.querySelectorAll("h1"),e.querySelectorAll("h2"),e.querySelectorAll("h3"),e.querySelectorAll("h4")].forEach(function(e){e.forEach(function(e){var t=createAnchorElement(e.id);e.classList.add("has-anchor"),e.append(t)})})}function copy(e){const t=document.createElement("textarea");t.value=e,document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}function showTooltip(e){var t=document.getElementById(e);t.classList.add("show-tooltip"),setTimeout(function(){t.classList.remove("show-tooltip")},3e3)}function copyFunction(e){var t=document.getElementById(e);copy((t.querySelector(".linenums")||t.querySelector("code")).innerText.trim().replace(/(^\t)/gm,"")),showTooltip("tooltip-"+e)}function hideTocOnSourcePage(){isSourcePage()&&(document.querySelector(".toc-container").style.display="none")}function getPreTopBar(e,t=""){e='";return'
'+('
'+t.toLocaleUpperCase()+"
")+e+"
"}function getPreDiv(){var e=document.createElement("div");return e.classList.add("pre-div"),e}function processAllPre(){var e=document.querySelectorAll("pre"),t=document.querySelector("#PeOAagUepe"),o=document.querySelector("#VuAckcnZhf"),n=0,i=0,c=(t&&(i=t.getBoundingClientRect().height),o&&(n=o.getBoundingClientRect().height),window.innerHeight-n-i-250);e.forEach(function(e,t){var o,n=e.parentNode;n&&"true"===n.getAttribute("data-skip-pre-process")||(n=getPreDiv(),o=getPreTopBar(t="ScDloZOMdL"+t,e.getAttribute("data-lang")||"code"),n.innerHTML=o,e.style.maxHeight=c+"px",e.id=t,e.classList.add("prettyprint"),e.parentNode.insertBefore(n,e),n.appendChild(e))})}function highlightAndBringLineIntoView(){var e=window.location.hash.replace("#line","");try{var t='[data-line-number="'+e+'"',o=document.querySelector(t);o.scrollIntoView(),o.parentNode.classList.add("selected")}catch(e){console.error(e)}}function getFontSize(){var e=16;try{e=Number.parseInt(html.style.fontSize.split("px")[0],10)}catch(e){console.log(e)}return e}function localUpdateFontSize(e){html.style.fontSize=e+"px";var t=document.querySelector("#b77a68a492f343baabea06fad81f651e");t&&(t.innerHTML=e)}function updateFontSize(e){localUpdateFontSize(e),localStorage.setItem(fontSizeLocalStorageKey,e)}function incrementFont(e){var t=getFontSize();t + +
+ ${e} +
+ + + + + `}function initTooltip(){tippy(".theme-toggle",{content:"Toggle Theme",delay:500}),tippy(".search-button",{content:"Search",delay:500}),tippy(".font-size",{content:"Change font size",delay:500}),tippy(".codepen-button",{content:"Open code in CodePen",placement:"left"}),tippy(".copy-code",{content:"Copy this code",placement:"left"}),tippy(".font-size",{content:fontSizeTooltip(),trigger:"click",interactive:!0,allowHTML:!0,placement:"left"})}function fixTable(){for(const t of document.querySelectorAll("table")){if(t.classList.contains("hljs-ln"))return;var e=document.createElement("div");e.classList.add("table-div"),t.parentNode.insertBefore(e,t),e.appendChild(t)}}function hideMobileMenu(){var e=document.querySelector("#mobile-sidebar"),t=document.querySelector("#mobile-menu"),o=t.querySelector("use");e&&e.classList.remove("show"),t&&t.setAttribute("data-isopen","false"),o&&o.setAttribute("xlink:href","#menu-icon")}function showMobileMenu(){var e=document.querySelector("#mobile-sidebar"),t=document.querySelector("#mobile-menu"),o=t.querySelector("use");e&&e.classList.add("show"),t&&t.setAttribute("data-isopen","true"),o&&o.setAttribute("xlink:href","#close-icon")}function onMobileMenuClick(){("true"===document.querySelector("#mobile-menu").getAttribute("data-isopen")?hideMobileMenu:showMobileMenu)()}function initMobileMenu(){var e=document.querySelector("#mobile-menu");e&&e.addEventListener("click",onMobileMenuClick)}function addHrefToSidebarTitle(){document.querySelectorAll(".sidebar-title-anchor").forEach(function(e){e.setAttribute("href",baseURL)})}function highlightActiveLinkInSidebar(){var e=document.location.href.split("/");const t=e[e.length-1];let o=document.querySelector(`.sidebar a[href*='${t}']`);if(!o)try{o=document.querySelector(`.sidebar a[href*='${t.split("#")[0]}']`)}catch(e){return void console.error(e)}o&&(o.parentElement.classList.add("active"),o.scrollIntoView())}function onDomContentLoaded(){var e=document.querySelectorAll(".theme-toggle");initMobileMenu(),e&&e.forEach(function(e){e.addEventListener("click",toggleTheme)}),hljs.addPlugin({"after:highlightElement":function(e){e.el.parentNode.setAttribute("data-lang","code")}}),hljs.highlightAll(),hljs.initLineNumbersOnLoad({singleLine:!0}),initAccordion(),addAnchor(),processAllPre(),hideTocOnSourcePage(),setTimeout(function(){bringIdToViewOnMount(),isSourcePage()&&highlightAndBringLineIntoView()},1e3),initTooltip(),fixTable(),addHrefToSidebarTitle(),highlightActiveLinkInSidebar()}updateTheme(getTheme()),function(){var e=getFontSize(),t=localStorage.getItem(fontSizeLocalStorageKey);t?(t=Number.parseInt(t,10))!==e&&updateFontSize(t):updateFontSize(e)}(),window.addEventListener("DOMContentLoaded",onDomContentLoaded),window.addEventListener("hashchange",e=>{e=new URL(e.newURL);""!==e.hash&&bringIdToViewOnMount(e.hash)}),window.addEventListener("storage",e=>{"undefined"!==e.newValue&&(initTooltip(),e.key===themeLocalStorageKey&&localUpdateTheme(e.newValue),e.key===fontSizeLocalStorageKey&&localUpdateFontSize(e.newValue))}); \ No newline at end of file diff --git a/api/7.2.0/scripts/resize.js b/api/7.2.0/scripts/resize.js new file mode 100644 index 0000000000..d8e3a9cf7b --- /dev/null +++ b/api/7.2.0/scripts/resize.js @@ -0,0 +1,90 @@ +/* global document */ +// This file is @deprecated + +var NAVBAR_OPTIONS = {}; + +(function() { + var NAVBAR_RESIZE_LOCAL_STORAGE_KEY = 'NAVBAR_RESIZE_LOCAL_STORAGE_KEY'; + + var navbar = document.querySelector('#navbar'); + var footer = document.querySelector('#footer'); + var mainSection = document.querySelector('#main'); + var localStorageResizeObject = JSON.parse( + // eslint-disable-next-line no-undef + localStorage.getItem(NAVBAR_RESIZE_LOCAL_STORAGE_KEY) + ); + + /** + * Check whether we have any resize value in local storage or not. + * If we have resize value then resize the navbar. + **/ + if (localStorageResizeObject) { + navbar.style.width = localStorageResizeObject.width; + mainSection.style.marginLeft = localStorageResizeObject.width; + footer.style.marginLeft = localStorageResizeObject.width; + } + + var navbarSlider = document.querySelector('#navbar-resize'); + + function resizeNavbar(event) { + var pageX = event.pageX, + pageXPlusPx = event.pageX + 'px', + min = Number.parseInt(NAVBAR_OPTIONS.min, 10) || 300, + max = Number.parseInt(NAVBAR_OPTIONS.max, 10) || 600; + + /** + * Just to add some checks. If min is smaller than 10 then + * user may accidentally end up reducing the size of navbar + * less than 10. In that case user will not able to resize navbar + * because navbar slider will be hidden. + */ + if (min < 10) { + min = 10; + } + + /** + * Only resize if pageX in range between min and max + * allowed value. + */ + if (min < pageX && pageX < max) { + navbar.style.width = pageXPlusPx; + mainSection.style.marginLeft = pageXPlusPx; + footer.style.marginLeft = pageXPlusPx; + } + } + + function setupEventListeners() { + // eslint-disable-next-line no-undef + window.addEventListener('mousemove', resizeNavbar); + // eslint-disable-next-line no-undef + window.addEventListener('touchmove', resizeNavbar); + } + + function afterRemovingEventListeners() { + // eslint-disable-next-line no-undef + localStorage.setItem( + NAVBAR_RESIZE_LOCAL_STORAGE_KEY, + JSON.stringify({ + width: navbar.style.width + }) + ); + } + + function removeEventListeners() { + // eslint-disable-next-line no-undef + window.removeEventListener('mousemove', resizeNavbar); + // eslint-disable-next-line no-undef + window.removeEventListener('touchend', resizeNavbar); + afterRemovingEventListeners(); + } + + navbarSlider.addEventListener('mousedown', setupEventListeners); + navbarSlider.addEventListener('touchstart', setupEventListeners); + // eslint-disable-next-line no-undef + window.addEventListener('mouseup', removeEventListeners); +})(); + +// eslint-disable-next-line no-unused-vars +function setupResizeOptions(options) { + NAVBAR_OPTIONS = options; +} diff --git a/api/7.2.0/scripts/search.js b/api/7.2.0/scripts/search.js new file mode 100644 index 0000000000..415e1cf8d1 --- /dev/null +++ b/api/7.2.0/scripts/search.js @@ -0,0 +1,265 @@ +/* global document */ + +const searchId = 'LiBfqbJVcV'; +const searchHash = '#' + searchId; +const searchContainer = document.querySelector('#PkfLWpAbet'); +const searchWrapper = document.querySelector('#iCxFxjkHbP'); +const searchCloseButton = document.querySelector('#VjLlGakifb'); +const searchInput = document.querySelector('#vpcKVYIppa'); +const resultBox = document.querySelector('#fWwVHRuDuN'); + +function showResultText(text) { + resultBox.innerHTML = `${text}`; +} + +function hideSearch() { + // eslint-disable-next-line no-undef + if (window.location.hash === searchHash) { + // eslint-disable-next-line no-undef + history.go(-1); + } + + // eslint-disable-next-line no-undef + window.onhashchange = null; + + if (searchContainer) { + searchContainer.style.display = 'none'; + } +} + +function listenCloseKey(event) { + if (event.key === 'Escape') { + hideSearch(); + // eslint-disable-next-line no-undef + window.removeEventListener('keyup', listenCloseKey); + } +} + +function showSearch() { + try { + // Closing mobile menu before opening + // search box. + // It is defined in core.js + // eslint-disable-next-line no-undef + hideMobileMenu(); + } catch (error) { + console.error(error); + } + + // eslint-disable-next-line no-undef + window.onhashchange = hideSearch; + + // eslint-disable-next-line no-undef + if (window.location.hash !== searchHash) { + // eslint-disable-next-line no-undef + history.pushState(null, null, searchHash); + } + + if (searchContainer) { + searchContainer.style.display = 'flex'; + // eslint-disable-next-line no-undef + window.addEventListener('keyup', listenCloseKey); + } + + if (searchInput) { + searchInput.focus(); + } +} + +async function fetchAllData() { + // eslint-disable-next-line no-undef + const { hostname, protocol, port } = location; + + // eslint-disable-next-line no-undef + const base = protocol + '//' + hostname + (port !== '' ? ':' + port : '') + baseURL; + // eslint-disable-next-line no-undef + const url = new URL('data/search.json', base); + const result = await fetch(url); + const { list } = await result.json(); + + return list; +} + +// eslint-disable-next-line no-unused-vars +function onClickSearchItem(event) { + const target = event.currentTarget; + + if (target) { + const href = target.getAttribute('href') || ''; + let elementId = href.split('#')[1] || ''; + let element = document.getElementById(elementId); + + if (!element) { + elementId = decodeURI(elementId); + element = document.getElementById(elementId); + } + + if (element) { + setTimeout(function() { + // eslint-disable-next-line no-undef + bringElementIntoView(element); // defined in core.js + }, 100); + } + } +} + +function buildSearchResult(result) { + let output = ''; + const removeHTMLTagsRegExp = /(<([^>]+)>)/ig; + + for (const res of result) { + const { title = '', description = '' } = res.item; + + const _link = res.item.link.replace('.*/, ''); + const _title = title.replace(removeHTMLTagsRegExp, ""); + const _description = description.replace(removeHTMLTagsRegExp, ""); + + output += ` + +
${_title}
+
${_description || 'No description available.'}
+
+ `; + } + + return output; +} + +function getSearchResult(list, keys, searchKey) { + const defaultOptions = { + shouldSort: true, + threshold: 0.4, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: keys + }; + + const options = { ...defaultOptions }; + + // eslint-disable-next-line no-undef + const searchIndex = Fuse.createIndex(options.keys, list); + + // eslint-disable-next-line no-undef + const fuse = new Fuse(list, options, searchIndex); + + const result = fuse.search(searchKey); + + if (result.length > 20) { + return result.slice(0, 20); + } + + return result; +} + +function debounce(func, wait, immediate) { + let timeout; + + return function() { + const args = arguments; + + clearTimeout(timeout); + timeout = setTimeout(() => { + timeout = null; + if (!immediate) { + // eslint-disable-next-line consistent-this, no-invalid-this + func.apply(this, args); + } + }, wait); + + if (immediate && !timeout) { + // eslint-disable-next-line consistent-this, no-invalid-this + func.apply(this, args); + } + }; +} + +let searchData; + +async function search(event) { + const value = event.target.value; + const keys = ['title', 'description']; + + if (!resultBox) { + console.error('Search result container not found'); + + return; + } + + if (!value) { + showResultText('Type anything to view search result'); + + return; + } + + if (!searchData) { + showResultText('Loading...'); + + try { + // eslint-disable-next-line require-atomic-updates + searchData = await fetchAllData(); + } catch (e) { + console.log(e); + showResultText('Failed to load result.'); + + return; + } + } + + const result = getSearchResult(searchData, keys, value); + + if (!result.length) { + showResultText('No result found! Try some different combination.'); + + return; + } + + // eslint-disable-next-line require-atomic-updates + resultBox.innerHTML = buildSearchResult(result); +} + +function onDomContentLoaded() { + const searchButton = document.querySelectorAll('.search-button'); + const debouncedSearch = debounce(search, 300); + + if (searchCloseButton) { + searchCloseButton.addEventListener('click', hideSearch); + } + + if (searchButton) { + searchButton.forEach(function(item) { + item.addEventListener('click', showSearch); + }); + } + + if (searchContainer) { + searchContainer.addEventListener('click', hideSearch); + } + + if (searchWrapper) { + searchWrapper.addEventListener('click', function(event) { + event.stopPropagation(); + }); + } + + if (searchInput) { + searchInput.addEventListener('keyup', debouncedSearch); + } + + // eslint-disable-next-line no-undef + if (window.location.hash === searchHash) { + showSearch(); + } +} + +// eslint-disable-next-line no-undef +window.addEventListener('DOMContentLoaded', onDomContentLoaded); + +// eslint-disable-next-line no-undef +window.addEventListener('hashchange', function() { + // eslint-disable-next-line no-undef + if (window.location.hash === searchHash) { + showSearch(); + } +}); diff --git a/api/7.2.0/scripts/search.min.js b/api/7.2.0/scripts/search.min.js new file mode 100644 index 0000000000..5358bced85 --- /dev/null +++ b/api/7.2.0/scripts/search.min.js @@ -0,0 +1,6 @@ +const searchId="LiBfqbJVcV",searchHash="#"+searchId,searchContainer=document.querySelector("#PkfLWpAbet"),searchWrapper=document.querySelector("#iCxFxjkHbP"),searchCloseButton=document.querySelector("#VjLlGakifb"),searchInput=document.querySelector("#vpcKVYIppa"),resultBox=document.querySelector("#fWwVHRuDuN");function showResultText(e){resultBox.innerHTML=`${e}`}function hideSearch(){window.location.hash===searchHash&&history.go(-1),window.onhashchange=null,searchContainer&&(searchContainer.style.display="none")}function listenCloseKey(e){"Escape"===e.key&&(hideSearch(),window.removeEventListener("keyup",listenCloseKey))}function showSearch(){try{hideMobileMenu()}catch(e){console.error(e)}window.onhashchange=hideSearch,window.location.hash!==searchHash&&history.pushState(null,null,searchHash),searchContainer&&(searchContainer.style.display="flex",window.addEventListener("keyup",listenCloseKey)),searchInput&&searchInput.focus()}async function fetchAllData(){var{hostname:e,protocol:t,port:n}=location,t=t+"//"+e+(""!==n?":"+n:"")+baseURL,e=new URL("data/search.json",t);const a=await fetch(e);n=(await a.json()).list;return n}function onClickSearchItem(t){const n=t.currentTarget;if(n){const a=n.getAttribute("href")||"";t=a.split("#")[1]||"";let e=document.getElementById(t);e||(t=decodeURI(t),e=document.getElementById(t)),e&&setTimeout(function(){bringElementIntoView(e)},100)}}function buildSearchResult(e){let t="";var n=/(<([^>]+)>)/gi;for(const s of e){const{title:c="",description:i=""}=s.item;var a=s.item.link.replace('.*/,""),o=c.replace(n,""),r=i.replace(n,"");t+=` + +
${o}
+
${r||"No description available."}
+
+ `}return t}function getSearchResult(e,t,n){var t={...{shouldSort:!0,threshold:.4,location:0,distance:100,maxPatternLength:32,minMatchCharLength:1,keys:t}},a=Fuse.createIndex(t.keys,e);const o=new Fuse(e,t,a),r=o.search(n);return 20{o=null,a||t.apply(this,e)},n),a&&!o&&t.apply(this,e)}}let searchData;async function search(e){e=e.target.value;if(resultBox)if(e){if(!searchData){showResultText("Loading...");try{searchData=await fetchAllData()}catch(e){return console.log(e),void showResultText("Failed to load result.")}}e=getSearchResult(searchData,["title","description"],e);e.length?resultBox.innerHTML=buildSearchResult(e):showResultText("No result found! Try some different combination.")}else showResultText("Type anything to view search result");else console.error("Search result container not found")}function onDomContentLoaded(){const e=document.querySelectorAll(".search-button");var t=debounce(search,300);searchCloseButton&&searchCloseButton.addEventListener("click",hideSearch),e&&e.forEach(function(e){e.addEventListener("click",showSearch)}),searchContainer&&searchContainer.addEventListener("click",hideSearch),searchWrapper&&searchWrapper.addEventListener("click",function(e){e.stopPropagation()}),searchInput&&searchInput.addEventListener("keyup",t),window.location.hash===searchHash&&showSearch()}window.addEventListener("DOMContentLoaded",onDomContentLoaded),window.addEventListener("hashchange",function(){window.location.hash===searchHash&&showSearch()}); \ No newline at end of file diff --git a/api/7.2.0/scripts/third-party/Apache-License-2.0.txt b/api/7.2.0/scripts/third-party/Apache-License-2.0.txt new file mode 100644 index 0000000000..75b52484ea --- /dev/null +++ b/api/7.2.0/scripts/third-party/Apache-License-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/api/7.2.0/scripts/third-party/fuse.js b/api/7.2.0/scripts/third-party/fuse.js new file mode 100644 index 0000000000..a55c5daa07 --- /dev/null +++ b/api/7.2.0/scripts/third-party/fuse.js @@ -0,0 +1,9 @@ +/** + * Fuse.js v6.4.6 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2021 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function n(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:3,t=new Map,n=Math.pow(10,e);return{get:function(e){var r=e.match(I).length;if(t.has(r))return t.get(r);var i=1/Math.sqrt(r),o=parseFloat(Math.round(i*n)/n);return t.set(r,o),o},clear:function(){t.clear()}}}var E=function(){function e(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=n.getFn,i=void 0===r?A.getFn:r;t(this,e),this.norm=C(3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return r(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?A.getFn:r,o=new E({getFn:i});return o.setKeys(e.map(_)),o.setSources(t),o.create(),o}function R(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?A.distance:s,h=t.ignoreLocation,f=void 0===h?A.ignoreLocation:h,l=r/e.length;if(f)return l;var d=Math.abs(a-o);return u?l+d/u:d?1:l}function F(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:A.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}function P(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?A.location:o,a=i.threshold,s=void 0===a?A.threshold:a,u=i.distance,h=void 0===u?A.distance:u,f=i.includeMatches,l=void 0===f?A.includeMatches:f,d=i.findAllMatches,v=void 0===d?A.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?A.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?A.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?A.ignoreLocation:k;if(t(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:l,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?n:n.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){r.chunks.push({pattern:e,alphabet:P(e),startIndex:t})},x=this.pattern.length;if(x>32){for(var L=0,S=x%32,w=x-S;L3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?A.location:i,c=r.distance,a=void 0===c?A.distance:c,s=r.threshold,u=void 0===s?A.threshold:s,h=r.findAllMatches,f=void 0===h?A.findAllMatches:h,l=r.minMatchCharLength,d=void 0===l?A.minMatchCharLength:l,v=r.includeMatches,g=void 0===v?A.includeMatches:v,y=r.ignoreLocation,p=void 0===y?A.ignoreLocation:y;if(t.length>32)throw new Error(L(32));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,S=b,w=d>1||g,_=w?Array(M):[];(m=e.indexOf(t,S))>-1;){var O=R(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),S=m+k,w)for(var j=0;j=K;J-=1){var T=J-1,U=n[e.charAt(T)];if(w&&(_[T]=+!!U),W[J]=(W[J+1]<<1|1)&U,P&&(W[J]|=(I[J+1]|I[J])<<1|1|I[J+1]),W[J]&$&&(C=R(t,{errors:P,currentLocation:T,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(S=T)<=b)break;K=Math.max(1,2*b-S)}}var V=R(t,{errors:P+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p});if(V>x)break;I=W}var B={isMatch:S>=0,score:Math.max(.001,C)};if(w){var G=F(_,d);G.length?g&&(B.indices=G):B.isMatch=!1}return B}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:f}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(l(d),l(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),D=function(){function e(n){t(this,e),this.pattern=n}return r(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return z(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return z(e,this.singleRegex)}}]),e}();function z(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(i,e);var n=f(i);function i(e){return t(this,i),n.call(this,e)}return r(i,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),i}(D),q=function(e){a(i,e);var n=f(i);function i(e){return t(this,i),n.call(this,e)}return r(i,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),i}(D),W=function(e){a(i,e);var n=f(i);function i(e){return t(this,i),n.call(this,e)}return r(i,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),i}(D),J=function(e){a(i,e);var n=f(i);function i(e){return t(this,i),n.call(this,e)}return r(i,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),i}(D),T=function(e){a(i,e);var n=f(i);function i(e){return t(this,i),n.call(this,e)}return r(i,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),i}(D),U=function(e){a(i,e);var n=f(i);function i(e){return t(this,i),n.call(this,e)}return r(i,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),i}(D),V=function(e){a(i,e);var n=f(i);function i(e){var r,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?A.location:c,s=o.threshold,u=void 0===s?A.threshold:s,h=o.distance,f=void 0===h?A.distance:h,l=o.includeMatches,d=void 0===l?A.includeMatches:l,v=o.findAllMatches,g=void 0===v?A.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?A.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?A.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?A.ignoreLocation:M;return t(this,i),(r=n.call(this,e))._bitapSearch=new N(e,{location:a,threshold:u,distance:f,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),r}return r(i,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),i}(D),B=function(e){a(i,e);var n=f(i);function i(e){return t(this,i),n.call(this,e)}return r(i,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),i}(D),G=[K,B,W,J,U,T,q,V],H=G.length,Q=/ +(?=([^\"]*\"[^\"]*\")*[^\"]*$)/;function X(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Q).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=r.isCaseSensitive,o=void 0===i?A.isCaseSensitive:i,c=r.includeMatches,a=void 0===c?A.includeMatches:c,s=r.minMatchCharLength,u=void 0===s?A.minMatchCharLength:s,h=r.ignoreLocation,f=void 0===h?A.ignoreLocation:h,l=r.findAllMatches,d=void 0===l?A.findAllMatches:l,v=r.location,g=void 0===v?A.location:v,y=r.threshold,p=void 0===y?A.threshold:y,m=r.distance,k=void 0===m?A.distance:m;t(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:f,location:g,threshold:p,distance:k},this.pattern=o?n:n.toLowerCase(),this.query=X(this.pattern,this.options)}return r(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function le(e,t){t.score=e.score}function de(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?A.includeMatches:r,o=n.includeScore,c=void 0===o?A.includeScore:o,a=[];return i&&a.push(fe),c&&a.push(le),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ve=function(){function e(n){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=arguments.length>2?arguments[2]:void 0;t(this,e),this.options=c({},A,{},r),this.options.useExtendedSearch,this._keyStore=new w(this.options.keys),this.setCollection(n,i)}return r(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof E))throw new Error("Incorrect 'index' type");this._myIndex=t||$(this.options.keys,this._docs,{getFn:this.options.getFn})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return he(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),de(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=te(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.auto,i=void 0===r||r,o=function e(n){var r=Object.keys(n),o=ae(n);if(!o&&r.length>1&&!ce(n))return e(ue(n));if(se(n)){var c=o?n[ie]:r[0],a=o?n[oe]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return i&&(s.searcher=te(a,t)),s}var u={children:[],operator:r[0]};return r.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u};return ce(e)||(e=ue(e)),o(e)}(e,this.options),r=this._myIndex.records,i={},o=[];return r.forEach((function(e){var r=e.$,c=e.i;if(k(r)){var a=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}switch(n.operator){case ne:for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?A.getFn:n,i=e.keys,o=e.records,c=new E({getFn:r});return c.setKeys(i),c.setIndexRecords(o),c},ve.config=A,function(){ee.push.apply(ee,arguments)}(Z),ve},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).Fuse=t(); \ No newline at end of file diff --git a/api/7.2.0/scripts/third-party/hljs-line-num-original.js b/api/7.2.0/scripts/third-party/hljs-line-num-original.js new file mode 100644 index 0000000000..9b8e18f77c --- /dev/null +++ b/api/7.2.0/scripts/third-party/hljs-line-num-original.js @@ -0,0 +1,369 @@ +// jshint multistr:true + +(function (w, d) { + 'use strict'; + + var TABLE_NAME = 'hljs-ln', + LINE_NAME = 'hljs-ln-line', + CODE_BLOCK_NAME = 'hljs-ln-code', + NUMBERS_BLOCK_NAME = 'hljs-ln-numbers', + NUMBER_LINE_NAME = 'hljs-ln-n', + DATA_ATTR_NAME = 'data-line-number', + BREAK_LINE_REGEXP = /\r\n|\r|\n/g; + + if (w.hljs) { + w.hljs.initLineNumbersOnLoad = initLineNumbersOnLoad; + w.hljs.lineNumbersBlock = lineNumbersBlock; + w.hljs.lineNumbersValue = lineNumbersValue; + + addStyles(); + } else { + w.console.error('highlight.js not detected!'); + } + + function isHljsLnCodeDescendant(domElt) { + var curElt = domElt; + while (curElt) { + if (curElt.className && curElt.className.indexOf('hljs-ln-code') !== -1) { + return true; + } + curElt = curElt.parentNode; + } + return false; + } + + function getHljsLnTable(hljsLnDomElt) { + var curElt = hljsLnDomElt; + while (curElt.nodeName !== 'TABLE') { + curElt = curElt.parentNode; + } + return curElt; + } + + // Function to workaround a copy issue with Microsoft Edge. + // Due to hljs-ln wrapping the lines of code inside a element, + // itself wrapped inside a
 element, window.getSelection().toString()
+    // does not contain any line breaks. So we need to get them back using the
+    // rendered code in the DOM as reference.
+    function edgeGetSelectedCodeLines(selection) {
+        // current selected text without line breaks
+        var selectionText = selection.toString();
+
+        // get the 
' + + '' + + '' + + '', + [ + LINE_NAME, + NUMBERS_BLOCK_NAME, + NUMBER_LINE_NAME, + DATA_ATTR_NAME, + CODE_BLOCK_NAME, + i + options.startFrom, + lines[i].length > 0 ? lines[i] : ' ' + ]); + } + + return format('
element wrapping the first line of selected code + var tdAnchor = selection.anchorNode; + while (tdAnchor.nodeName !== 'TD') { + tdAnchor = tdAnchor.parentNode; + } + + // get the element wrapping the last line of selected code + var tdFocus = selection.focusNode; + while (tdFocus.nodeName !== 'TD') { + tdFocus = tdFocus.parentNode; + } + + // extract line numbers + var firstLineNumber = parseInt(tdAnchor.dataset.lineNumber); + var lastLineNumber = parseInt(tdFocus.dataset.lineNumber); + + // multi-lines copied case + if (firstLineNumber != lastLineNumber) { + + var firstLineText = tdAnchor.textContent; + var lastLineText = tdFocus.textContent; + + // if the selection was made backward, swap values + if (firstLineNumber > lastLineNumber) { + var tmp = firstLineNumber; + firstLineNumber = lastLineNumber; + lastLineNumber = tmp; + tmp = firstLineText; + firstLineText = lastLineText; + lastLineText = tmp; + } + + // discard not copied characters in first line + while (selectionText.indexOf(firstLineText) !== 0) { + firstLineText = firstLineText.slice(1); + } + + // discard not copied characters in last line + while (selectionText.lastIndexOf(lastLineText) === -1) { + lastLineText = lastLineText.slice(0, -1); + } + + // reconstruct and return the real copied text + var selectedText = firstLineText; + var hljsLnTable = getHljsLnTable(tdAnchor); + for (var i = firstLineNumber + 1 ; i < lastLineNumber ; ++i) { + var codeLineSel = format('.{0}[{1}="{2}"]', [CODE_BLOCK_NAME, DATA_ATTR_NAME, i]); + var codeLineElt = hljsLnTable.querySelector(codeLineSel); + selectedText += '\n' + codeLineElt.textContent; + } + selectedText += '\n' + lastLineText; + return selectedText; + // single copied line case + } else { + return selectionText; + } + } + + // ensure consistent code copy/paste behavior across all browsers + // (see https://github.com/wcoder/highlightjs-line-numbers.js/issues/51) + document.addEventListener('copy', function(e) { + // get current selection + var selection = window.getSelection(); + // override behavior when one wants to copy line of codes + if (isHljsLnCodeDescendant(selection.anchorNode)) { + var selectionText; + // workaround an issue with Microsoft Edge as copied line breaks + // are removed otherwise from the selection string + if (window.navigator.userAgent.indexOf('Edge') !== -1) { + selectionText = edgeGetSelectedCodeLines(selection); + } else { + // other browsers can directly use the selection string + selectionText = selection.toString(); + } + e.clipboardData.setData( + 'text/plain', + selectionText + .replace(/(^\t)/gm, '') + ); + e.preventDefault(); + } + }); + + function addStyles () { + var css = d.createElement('style'); + css.type = 'text/css'; + css.innerHTML = format( + '.{0}{border-collapse:collapse}' + + '.{0} td{padding:0}' + + '.{1}:before{content:attr({2})}', + [ + TABLE_NAME, + NUMBER_LINE_NAME, + DATA_ATTR_NAME + ]); + d.getElementsByTagName('head')[0].appendChild(css); + } + + function initLineNumbersOnLoad (options) { + if (d.readyState === 'interactive' || d.readyState === 'complete') { + documentReady(options); + } else { + w.addEventListener('DOMContentLoaded', function () { + documentReady(options); + }); + } + } + + function documentReady (options) { + try { + var blocks = d.querySelectorAll('code.hljs,code.nohighlight'); + + for (var i in blocks) { + if (blocks.hasOwnProperty(i)) { + if (!isPluginDisabledForBlock(blocks[i])) { + lineNumbersBlock(blocks[i], options); + } + } + } + } catch (e) { + w.console.error('LineNumbers error: ', e); + } + } + + function isPluginDisabledForBlock(element) { + return element.classList.contains('nohljsln'); + } + + function lineNumbersBlock (element, options) { + if (typeof element !== 'object') return; + + async(function () { + element.innerHTML = lineNumbersInternal(element, options); + }); + } + + function lineNumbersValue (value, options) { + if (typeof value !== 'string') return; + + var element = document.createElement('code') + element.innerHTML = value + + return lineNumbersInternal(element, options); + } + + function lineNumbersInternal (element, options) { + + var internalOptions = mapOptions(element, options); + + duplicateMultilineNodes(element); + + return addLineNumbersBlockFor(element.innerHTML, internalOptions); + } + + function addLineNumbersBlockFor (inputHtml, options) { + var lines = getLines(inputHtml); + + // if last line contains only carriage return remove it + if (lines[lines.length-1].trim() === '') { + lines.pop(); + } + + if (lines.length > 1 || options.singleLine) { + var html = ''; + + for (var i = 0, l = lines.length; i < l; i++) { + html += format( + '
' + + '' + + '{6}' + + '
{1}
', [ TABLE_NAME, html ]); + } + + return inputHtml; + } + + /** + * @param {HTMLElement} element Code block. + * @param {Object} options External API options. + * @returns {Object} Internal API options. + */ + function mapOptions (element, options) { + options = options || {}; + return { + singleLine: getSingleLineOption(options), + startFrom: getStartFromOption(element, options) + }; + } + + function getSingleLineOption (options) { + var defaultValue = false; + if (!!options.singleLine) { + return options.singleLine; + } + return defaultValue; + } + + function getStartFromOption (element, options) { + var defaultValue = 1; + var startFrom = defaultValue; + + if (isFinite(options.startFrom)) { + startFrom = options.startFrom; + } + + // can be overridden because local option is priority + var value = getAttribute(element, 'data-ln-start-from'); + if (value !== null) { + startFrom = toNumber(value, defaultValue); + } + + return startFrom; + } + + /** + * Recursive method for fix multi-line elements implementation in highlight.js + * Doing deep passage on child nodes. + * @param {HTMLElement} element + */ + function duplicateMultilineNodes (element) { + var nodes = element.childNodes; + for (var node in nodes) { + if (nodes.hasOwnProperty(node)) { + var child = nodes[node]; + if (getLinesCount(child.textContent) > 0) { + if (child.childNodes.length > 0) { + duplicateMultilineNodes(child); + } else { + duplicateMultilineNode(child.parentNode); + } + } + } + } + } + + /** + * Method for fix multi-line elements implementation in highlight.js + * @param {HTMLElement} element + */ + function duplicateMultilineNode (element) { + var className = element.className; + + if ( ! /hljs-/.test(className)) return; + + var lines = getLines(element.innerHTML); + + for (var i = 0, result = ''; i < lines.length; i++) { + var lineText = lines[i].length > 0 ? lines[i] : ' '; + result += format('{1}\n', [ className, lineText ]); + } + + element.innerHTML = result.trim(); + } + + function getLines (text) { + if (text.length === 0) return []; + return text.split(BREAK_LINE_REGEXP); + } + + function getLinesCount (text) { + return (text.trim().match(BREAK_LINE_REGEXP) || []).length; + } + + /// + /// HELPERS + /// + + function async (func) { + w.setTimeout(func, 0); + } + + /** + * {@link https://wcoder.github.io/notes/string-format-for-string-formating-in-javascript} + * @param {string} format + * @param {array} args + */ + function format (format, args) { + return format.replace(/\{(\d+)\}/g, function(m, n){ + return args[n] !== undefined ? args[n] : m; + }); + } + + /** + * @param {HTMLElement} element Code block. + * @param {String} attrName Attribute name. + * @returns {String} Attribute value or empty. + */ + function getAttribute (element, attrName) { + return element.hasAttribute(attrName) ? element.getAttribute(attrName) : null; + } + + /** + * @param {String} str Source string. + * @param {Number} fallback Fallback value. + * @returns Parsed number or fallback value. + */ + function toNumber (str, fallback) { + if (!str) return fallback; + var number = Number(str); + return isFinite(number) ? number : fallback; + } + +}(window, document)); diff --git a/api/7.2.0/scripts/third-party/hljs-line-num.js b/api/7.2.0/scripts/third-party/hljs-line-num.js new file mode 100644 index 0000000000..facdf6bed4 --- /dev/null +++ b/api/7.2.0/scripts/third-party/hljs-line-num.js @@ -0,0 +1 @@ +!function(r,o){"use strict";var e,l="hljs-ln",s="hljs-ln-line",f="hljs-ln-code",c="hljs-ln-numbers",u="hljs-ln-n",h="data-line-number",n=/\r\n|\r|\n/g;function t(e){for(var n=e.toString(),t=e.anchorNode;"TD"!==t.nodeName;)t=t.parentNode;for(var r=e.focusNode;"TD"!==r.nodeName;)r=r.parentNode;var e=parseInt(t.dataset.lineNumber),o=parseInt(r.dataset.lineNumber);if(e==o)return n;var a,i=t.textContent,l=r.textContent;for(o{6}',[s,c,u,h,f,a+t.startFrom,0{1}',[l,o])}return e}function m(e){var n=e.className;if(/hljs-/.test(n)){for(var t=g(e.innerHTML),r=0,o="";r{1}\n',[n,0/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + /** + * performs a shallow merge of multiple objects into one + * + * @template T + * @param {T} original + * @param {Record[]} objects + * @returns {T} a single new object + */ + function inherit$1(original, ...objects) { + /** @type Record */ + const result = Object.create(null); + + for (const key in original) { + result[key] = original[key]; + } + objects.forEach(function (obj) { + for (const key in obj) { + result[key] = obj[key]; + } + }); + return /** @type {T} */ (result); + } + + /** + * @typedef {object} Renderer + * @property {(text: string) => void} addText + * @property {(node: Node) => void} openNode + * @property {(node: Node) => void} closeNode + * @property {() => string} value + */ + + /** @typedef {{kind?: string, sublanguage?: boolean}} Node */ + /** @typedef {{walk: (r: Renderer) => void}} Tree */ + /** */ + + const SPAN_CLOSE = ''; + + /** + * Determines if a node needs to be wrapped in + * + * @param {Node} node */ + const emitsWrappingTags = (node) => { + return !!node.kind; + }; + + /** + * + * @param {string} name + * @param {{prefix:string}} options + */ + const expandScopeName = (name, { prefix }) => { + if (name.includes(".")) { + const pieces = name.split("."); + return [ + `${prefix}${pieces.shift()}`, + ...(pieces.map((x, i) => `${x}${"_".repeat(i + 1)}`)) + ].join(" "); + } + return `${prefix}${name}`; + }; + + /** @type {Renderer} */ + class HTMLRenderer { + /** + * Creates a new HTMLRenderer + * + * @param {Tree} parseTree - the parse tree (must support `walk` API) + * @param {{classPrefix: string}} options + */ + constructor(parseTree, options) { + this.buffer = ""; + this.classPrefix = options.classPrefix; + parseTree.walk(this); + } + + /** + * Adds texts to the output stream + * + * @param {string} text */ + addText(text) { + this.buffer += escapeHTML(text); + } + + /** + * Adds a node open to the output stream (if needed) + * + * @param {Node} node */ + openNode(node) { + if (!emitsWrappingTags(node)) return; + + let scope = node.kind; + if (node.sublanguage) { + scope = `language-${scope}`; + } else { + scope = expandScopeName(scope, { prefix: this.classPrefix }); + } + this.span(scope); + } + + /** + * Adds a node close to the output stream (if needed) + * + * @param {Node} node */ + closeNode(node) { + if (!emitsWrappingTags(node)) return; + + this.buffer += SPAN_CLOSE; + } + + /** + * returns the accumulated buffer + */ + value() { + return this.buffer; + } + + // helpers + + /** + * Builds a span element + * + * @param {string} className */ + span(className) { + this.buffer += ``; + } + } + + /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */ + /** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */ + /** @typedef {import('highlight.js').Emitter} Emitter */ + /** */ + + class TokenTree { + constructor() { + /** @type DataNode */ + this.rootNode = { children: [] }; + this.stack = [this.rootNode]; + } + + get top() { + return this.stack[this.stack.length - 1]; + } + + get root() { return this.rootNode; } + + /** @param {Node} node */ + add(node) { + this.top.children.push(node); + } + + /** @param {string} kind */ + openNode(kind) { + /** @type Node */ + const node = { kind, children: [] }; + this.add(node); + this.stack.push(node); + } + + closeNode() { + if (this.stack.length > 1) { + return this.stack.pop(); + } + // eslint-disable-next-line no-undefined + return undefined; + } + + closeAllNodes() { + while (this.closeNode()); + } + + toJSON() { + return JSON.stringify(this.rootNode, null, 4); + } + + /** + * @typedef { import("./html_renderer").Renderer } Renderer + * @param {Renderer} builder + */ + walk(builder) { + // this does not + return this.constructor._walk(builder, this.rootNode); + // this works + // return TokenTree._walk(builder, this.rootNode); + } + + /** + * @param {Renderer} builder + * @param {Node} node + */ + static _walk(builder, node) { + if (typeof node === "string") { + builder.addText(node); + } else if (node.children) { + builder.openNode(node); + node.children.forEach((child) => this._walk(builder, child)); + builder.closeNode(node); + } + return builder; + } + + /** + * @param {Node} node + */ + static _collapse(node) { + if (typeof node === "string") return; + if (!node.children) return; + + if (node.children.every(el => typeof el === "string")) { + // node.text = node.children.join(""); + // delete node.children; + node.children = [node.children.join("")]; + } else { + node.children.forEach((child) => { + TokenTree._collapse(child); + }); + } + } + } + + /** + Currently this is all private API, but this is the minimal API necessary + that an Emitter must implement to fully support the parser. + + Minimal interface: + + - addKeyword(text, kind) + - addText(text) + - addSublanguage(emitter, subLanguageName) + - finalize() + - openNode(kind) + - closeNode() + - closeAllNodes() + - toHTML() + + */ + + /** + * @implements {Emitter} + */ + class TokenTreeEmitter extends TokenTree { + /** + * @param {*} options + */ + constructor(options) { + super(); + this.options = options; + } + + /** + * @param {string} text + * @param {string} kind + */ + addKeyword(text, kind) { + if (text === "") { return; } + + this.openNode(kind); + this.addText(text); + this.closeNode(); + } + + /** + * @param {string} text + */ + addText(text) { + if (text === "") { return; } + + this.add(text); + } + + /** + * @param {Emitter & {root: DataNode}} emitter + * @param {string} name + */ + addSublanguage(emitter, name) { + /** @type DataNode */ + const node = emitter.root; + node.kind = name; + node.sublanguage = true; + this.add(node); + } + + toHTML() { + const renderer = new HTMLRenderer(this, this.options); + return renderer.value(); + } + + finalize() { + return true; + } + } + + /** + * @param {string} value + * @returns {RegExp} + * */ + + /** + * @param {RegExp | string } re + * @returns {string} + */ + function source(re) { + if (!re) return null; + if (typeof re === "string") return re; + + return re.source; + } + + /** + * @param {RegExp | string } re + * @returns {string} + */ + function lookahead(re) { + return concat('(?=', re, ')'); + } + + /** + * @param {RegExp | string } re + * @returns {string} + */ + function optional(re) { + return concat('(?:', re, ')?'); + } + + /** + * @param {...(RegExp | string) } args + * @returns {string} + */ + function concat(...args) { + const joined = args.map((x) => source(x)).join(""); + return joined; + } + + function stripOptionsFromArgs(args) { + const opts = args[args.length - 1]; + + if (typeof opts === 'object' && opts.constructor === Object) { + args.splice(args.length - 1, 1); + return opts; + } else { + return {}; + } + } + + /** + * Any of the passed expresssions may match + * + * Creates a huge this | this | that | that match + * @param {(RegExp | string)[] } args + * @returns {string} + */ + function either(...args) { + const opts = stripOptionsFromArgs(args); + const joined = '(' + + (opts.capture ? "" : "?:") + + args.map((x) => source(x)).join("|") + ")"; + return joined; + } + + /** + * @param {RegExp} re + * @returns {number} + */ + function countMatchGroups(re) { + return (new RegExp(re.toString() + '|')).exec('').length - 1; + } + + /** + * Does lexeme start with a regular expression match at the beginning + * @param {RegExp} re + * @param {string} lexeme + */ + function startsWith(re, lexeme) { + const match = re && re.exec(lexeme); + return match && match.index === 0; + } + + // BACKREF_RE matches an open parenthesis or backreference. To avoid + // an incorrect parse, it additionally matches the following: + // - [...] elements, where the meaning of parentheses and escapes change + // - other escape sequences, so we do not misparse escape sequences as + // interesting elements + // - non-matching or lookahead parentheses, which do not capture. These + // follow the '(' with a '?'. + const BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; + + // **INTERNAL** Not intended for outside usage + // join logically computes regexps.join(separator), but fixes the + // backreferences so they continue to match. + // it also places each individual regular expression into it's own + // match group, keeping track of the sequencing of those match groups + // is currently an exercise for the caller. :-) + /** + * @param {(string | RegExp)[]} regexps + * @param {{joinWith: string}} opts + * @returns {string} + */ + function _rewriteBackreferences(regexps, { joinWith }) { + let numCaptures = 0; + + return regexps.map((regex) => { + numCaptures += 1; + const offset = numCaptures; + let re = source(regex); + let out = ''; + + while (re.length > 0) { + const match = BACKREF_RE.exec(re); + if (!match) { + out += re; + break; + } + out += re.substring(0, match.index); + re = re.substring(match.index + match[0].length); + if (match[0][0] === '\\' && match[1]) { + // Adjust the backreference. + out += '\\' + String(Number(match[1]) + offset); + } else { + out += match[0]; + if (match[0] === '(') { + numCaptures++; + } + } + } + return out; + }).map(re => `(${re})`).join(joinWith); + } + + /** @typedef {import('highlight.js').Mode} Mode */ + /** @typedef {import('highlight.js').ModeCallback} ModeCallback */ + + // Common regexps + const MATCH_NOTHING_RE = /\b\B/; + const IDENT_RE$1 = '[a-zA-Z]\\w*'; + const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; + const NUMBER_RE = '\\b\\d+(\\.\\d+)?'; + const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float + const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... + const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; + + /** + * @param { Partial & {binary?: string | RegExp} } opts + */ + const SHEBANG = (opts = {}) => { + const beginShebang = /^#![ ]*\//; + if (opts.binary) { + opts.begin = concat( + beginShebang, + /.*\b/, + opts.binary, + /\b.*/); + } + return inherit$1({ + scope: 'meta', + begin: beginShebang, + end: /$/, + relevance: 0, + /** @type {ModeCallback} */ + "on:begin": (m, resp) => { + if (m.index !== 0) resp.ignoreMatch(); + } + }, opts); + }; + + // Common modes + const BACKSLASH_ESCAPE = { + begin: '\\\\[\\s\\S]', relevance: 0 + }; + const APOS_STRING_MODE = { + scope: 'string', + begin: '\'', + end: '\'', + illegal: '\\n', + contains: [BACKSLASH_ESCAPE] + }; + const QUOTE_STRING_MODE = { + scope: 'string', + begin: '"', + end: '"', + illegal: '\\n', + contains: [BACKSLASH_ESCAPE] + }; + const PHRASAL_WORDS_MODE = { + begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ + }; + /** + * Creates a comment mode + * + * @param {string | RegExp} begin + * @param {string | RegExp} end + * @param {Mode | {}} [modeOptions] + * @returns {Partial} + */ + const COMMENT = function (begin, end, modeOptions = {}) { + const mode = inherit$1( + { + scope: 'comment', + begin, + end, + contains: [] + }, + modeOptions + ); + mode.contains.push({ + scope: 'doctag', + // hack to avoid the space from being included. the space is necessary to + // match here to prevent the plain text rule below from gobbling up doctags + begin: '[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)', + end: /(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/, + excludeBegin: true, + relevance: 0 + }); + const ENGLISH_WORD = either( + // list of common 1 and 2 letter words in English + "I", + "a", + "is", + "so", + "us", + "to", + "at", + "if", + "in", + "it", + "on", + // note: this is not an exhaustive list of contractions, just popular ones + /[A-Za-z]+['](d|ve|re|ll|t|s|n)/, // contractions - can't we'd they're let's, etc + /[A-Za-z]+[-][a-z]+/, // `no-way`, etc. + /[A-Za-z][a-z]{2,}/ // allow capitalized words at beginning of sentences + ); + // looking like plain text, more likely to be a comment + mode.contains.push( + { + // TODO: how to include ", (, ) without breaking grammars that use these for + // comment delimiters? + // begin: /[ ]+([()"]?([A-Za-z'-]{3,}|is|a|I|so|us|[tT][oO]|at|if|in|it|on)[.]?[()":]?([.][ ]|[ ]|\))){3}/ + // --- + + // this tries to find sequences of 3 english words in a row (without any + // "programming" type syntax) this gives us a strong signal that we've + // TRULY found a comment - vs perhaps scanning with the wrong language. + // It's possible to find something that LOOKS like the start of the + // comment - but then if there is no readable text - good chance it is a + // false match and not a comment. + // + // for a visual example please see: + // https://github.com/highlightjs/highlight.js/issues/2827 + + begin: concat( + /[ ]+/, // necessary to prevent us gobbling up doctags like /* @author Bob Mcgill */ + '(', + ENGLISH_WORD, + /[.]?[:]?([.][ ]|[ ])/, + '){3}') // look for 3 words in a row + } + ); + return mode; + }; + const C_LINE_COMMENT_MODE = COMMENT('//', '$'); + const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/'); + const HASH_COMMENT_MODE = COMMENT('#', '$'); + const NUMBER_MODE = { + scope: 'number', + begin: NUMBER_RE, + relevance: 0 + }; + const C_NUMBER_MODE = { + scope: 'number', + begin: C_NUMBER_RE, + relevance: 0 + }; + const BINARY_NUMBER_MODE = { + scope: 'number', + begin: BINARY_NUMBER_RE, + relevance: 0 + }; + const REGEXP_MODE = { + // this outer rule makes sure we actually have a WHOLE regex and not simply + // an expression such as: + // + // 3 / something + // + // (which will then blow up when regex's `illegal` sees the newline) + begin: /(?=\/[^/\n]*\/)/, + contains: [{ + scope: 'regexp', + begin: /\//, + end: /\/[gimuy]*/, + illegal: /\n/, + contains: [ + BACKSLASH_ESCAPE, + { + begin: /\[/, + end: /\]/, + relevance: 0, + contains: [BACKSLASH_ESCAPE] + } + ] + }] + }; + const TITLE_MODE = { + scope: 'title', + begin: IDENT_RE$1, + relevance: 0 + }; + const UNDERSCORE_TITLE_MODE = { + scope: 'title', + begin: UNDERSCORE_IDENT_RE, + relevance: 0 + }; + const METHOD_GUARD = { + // excludes method names from keyword processing + begin: '\\.\\s*' + UNDERSCORE_IDENT_RE, + relevance: 0 + }; + + /** + * Adds end same as begin mechanics to a mode + * + * Your mode must include at least a single () match group as that first match + * group is what is used for comparison + * @param {Partial} mode + */ + const END_SAME_AS_BEGIN = function (mode) { + return Object.assign(mode, + { + /** @type {ModeCallback} */ + 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; }, + /** @type {ModeCallback} */ + 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); } + }); + }; + + var MODES$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + MATCH_NOTHING_RE: MATCH_NOTHING_RE, + IDENT_RE: IDENT_RE$1, + UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE, + NUMBER_RE: NUMBER_RE, + C_NUMBER_RE: C_NUMBER_RE, + BINARY_NUMBER_RE: BINARY_NUMBER_RE, + RE_STARTERS_RE: RE_STARTERS_RE, + SHEBANG: SHEBANG, + BACKSLASH_ESCAPE: BACKSLASH_ESCAPE, + APOS_STRING_MODE: APOS_STRING_MODE, + QUOTE_STRING_MODE: QUOTE_STRING_MODE, + PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE, + COMMENT: COMMENT, + C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE, + C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE, + HASH_COMMENT_MODE: HASH_COMMENT_MODE, + NUMBER_MODE: NUMBER_MODE, + C_NUMBER_MODE: C_NUMBER_MODE, + BINARY_NUMBER_MODE: BINARY_NUMBER_MODE, + REGEXP_MODE: REGEXP_MODE, + TITLE_MODE: TITLE_MODE, + UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE, + METHOD_GUARD: METHOD_GUARD, + END_SAME_AS_BEGIN: END_SAME_AS_BEGIN + }); + + /** + @typedef {import('highlight.js').CallbackResponse} CallbackResponse + @typedef {import('highlight.js').CompilerExt} CompilerExt + */ + + // Grammar extensions / plugins + // See: https://github.com/highlightjs/highlight.js/issues/2833 + + // Grammar extensions allow "syntactic sugar" to be added to the grammar modes + // without requiring any underlying changes to the compiler internals. + + // `compileMatch` being the perfect small example of now allowing a grammar + // author to write `match` when they desire to match a single expression rather + // than being forced to use `begin`. The extension then just moves `match` into + // `begin` when it runs. Ie, no features have been added, but we've just made + // the experience of writing (and reading grammars) a little bit nicer. + + // ------ + + // TODO: We need negative look-behind support to do this properly + /** + * Skip a match if it has a preceding dot + * + * This is used for `beginKeywords` to prevent matching expressions such as + * `bob.keyword.do()`. The mode compiler automatically wires this up as a + * special _internal_ 'on:begin' callback for modes with `beginKeywords` + * @param {RegExpMatchArray} match + * @param {CallbackResponse} response + */ + function skipIfHasPrecedingDot(match, response) { + const before = match.input[match.index - 1]; + if (before === ".") { + response.ignoreMatch(); + } + } + + /** + * + * @type {CompilerExt} + */ + function scopeClassName(mode, _parent) { + // eslint-disable-next-line no-undefined + if (mode.className !== undefined) { + mode.scope = mode.className; + delete mode.className; + } + } + + /** + * `beginKeywords` syntactic sugar + * @type {CompilerExt} + */ + function beginKeywords(mode, parent) { + if (!parent) return; + if (!mode.beginKeywords) return; + + // for languages with keywords that include non-word characters checking for + // a word boundary is not sufficient, so instead we check for a word boundary + // or whitespace - this does no harm in any case since our keyword engine + // doesn't allow spaces in keywords anyways and we still check for the boundary + // first + mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)'; + mode.__beforeBegin = skipIfHasPrecedingDot; + mode.keywords = mode.keywords || mode.beginKeywords; + delete mode.beginKeywords; + + // prevents double relevance, the keywords themselves provide + // relevance, the mode doesn't need to double it + // eslint-disable-next-line no-undefined + if (mode.relevance === undefined) mode.relevance = 0; + } + + /** + * Allow `illegal` to contain an array of illegal values + * @type {CompilerExt} + */ + function compileIllegal(mode, _parent) { + if (!Array.isArray(mode.illegal)) return; + + mode.illegal = either(...mode.illegal); + } + + /** + * `match` to match a single expression for readability + * @type {CompilerExt} + */ + function compileMatch(mode, _parent) { + if (!mode.match) return; + if (mode.begin || mode.end) throw new Error("begin & end are not supported with match"); + + mode.begin = mode.match; + delete mode.match; + } + + /** + * provides the default 1 relevance to all modes + * @type {CompilerExt} + */ + function compileRelevance(mode, _parent) { + // eslint-disable-next-line no-undefined + if (mode.relevance === undefined) mode.relevance = 1; + } + + // allow beforeMatch to act as a "qualifier" for the match + // the full match begin must be [beforeMatch][begin] + const beforeMatchExt = (mode, parent) => { + if (!mode.beforeMatch) return; + // starts conflicts with endsParent which we need to make sure the child + // rule is not matched multiple times + if (mode.starts) throw new Error("beforeMatch cannot be used with starts"); + + const originalMode = Object.assign({}, mode); + Object.keys(mode).forEach((key) => { delete mode[key]; }); + + mode.keywords = originalMode.keywords; + mode.begin = concat(originalMode.beforeMatch, lookahead(originalMode.begin)); + mode.starts = { + relevance: 0, + contains: [ + Object.assign(originalMode, { endsParent: true }) + ] + }; + mode.relevance = 0; + + delete originalMode.beforeMatch; + }; + + // keywords that should have no default relevance value + const COMMON_KEYWORDS = [ + 'of', + 'and', + 'for', + 'in', + 'not', + 'or', + 'if', + 'then', + 'parent', // common variable name + 'list', // common variable name + 'value' // common variable name + ]; + + const DEFAULT_KEYWORD_SCOPE = "keyword"; + + /** + * Given raw keywords from a language definition, compile them. + * + * @param {string | Record | Array} rawKeywords + * @param {boolean} caseInsensitive + */ + function compileKeywords(rawKeywords, caseInsensitive, scopeName = DEFAULT_KEYWORD_SCOPE) { + /** @type KeywordDict */ + const compiledKeywords = Object.create(null); + + // input can be a string of keywords, an array of keywords, or a object with + // named keys representing scopeName (which can then point to a string or array) + if (typeof rawKeywords === 'string') { + compileList(scopeName, rawKeywords.split(" ")); + } else if (Array.isArray(rawKeywords)) { + compileList(scopeName, rawKeywords); + } else { + Object.keys(rawKeywords).forEach(function (scopeName) { + // collapse all our objects back into the parent object + Object.assign( + compiledKeywords, + compileKeywords(rawKeywords[scopeName], caseInsensitive, scopeName) + ); + }); + } + return compiledKeywords; + + // --- + + /** + * Compiles an individual list of keywords + * + * Ex: "for if when while|5" + * + * @param {string} scopeName + * @param {Array} keywordList + */ + function compileList(scopeName, keywordList) { + if (caseInsensitive) { + keywordList = keywordList.map(x => x.toLowerCase()); + } + keywordList.forEach(function (keyword) { + const pair = keyword.split('|'); + compiledKeywords[pair[0]] = [scopeName, scoreForKeyword(pair[0], pair[1])]; + }); + } + } + + /** + * Returns the proper score for a given keyword + * + * Also takes into account comment keywords, which will be scored 0 UNLESS + * another score has been manually assigned. + * @param {string} keyword + * @param {string} [providedScore] + */ + function scoreForKeyword(keyword, providedScore) { + // manual scores always win over common keywords + // so you can force a score of 1 if you really insist + if (providedScore) { + return Number(providedScore); + } + + return commonKeyword(keyword) ? 0 : 1; + } + + /** + * Determines if a given keyword is common or not + * + * @param {string} keyword */ + function commonKeyword(keyword) { + return COMMON_KEYWORDS.includes(keyword.toLowerCase()); + } + + /* + + For the reasoning behind this please see: + https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419 + + */ + + /** + * @type {Record} + */ + const seenDeprecations = {}; + + /** + * @param {string} message + */ + const error = (message) => { + console.error(message); + }; + + /** + * @param {string} message + * @param {any} args + */ + const warn = (message, ...args) => { + console.log(`WARN: ${message}`, ...args); + }; + + /** + * @param {string} version + * @param {string} message + */ + const deprecated = (version, message) => { + if (seenDeprecations[`${version}/${message}`]) return; + + console.log(`Deprecated as of ${version}. ${message}`); + seenDeprecations[`${version}/${message}`] = true; + }; + + /* eslint-disable no-throw-literal */ + + /** + @typedef {import('highlight.js').CompiledMode} CompiledMode + */ + + const MultiClassError = new Error(); + + /** + * Renumbers labeled scope names to account for additional inner match + * groups that otherwise would break everything. + * + * Lets say we 3 match scopes: + * + * { 1 => ..., 2 => ..., 3 => ... } + * + * So what we need is a clean match like this: + * + * (a)(b)(c) => [ "a", "b", "c" ] + * + * But this falls apart with inner match groups: + * + * (a)(((b)))(c) => ["a", "b", "b", "b", "c" ] + * + * Our scopes are now "out of alignment" and we're repeating `b` 3 times. + * What needs to happen is the numbers are remapped: + * + * { 1 => ..., 2 => ..., 5 => ... } + * + * We also need to know that the ONLY groups that should be output + * are 1, 2, and 5. This function handles this behavior. + * + * @param {CompiledMode} mode + * @param {Array} regexes + * @param {{key: "beginScope"|"endScope"}} opts + */ + function remapScopeNames(mode, regexes, { key }) { + let offset = 0; + const scopeNames = mode[key]; + /** @type Record */ + const emit = {}; + /** @type Record */ + const positions = {}; + + for (let i = 1; i <= regexes.length; i++) { + positions[i + offset] = scopeNames[i]; + emit[i + offset] = true; + offset += countMatchGroups(regexes[i - 1]); + } + // we use _emit to keep track of which match groups are "top-level" to avoid double + // output from inside match groups + mode[key] = positions; + mode[key]._emit = emit; + mode[key]._multi = true; + } + + /** + * @param {CompiledMode} mode + */ + function beginMultiClass(mode) { + if (!Array.isArray(mode.begin)) return; + + if (mode.skip || mode.excludeBegin || mode.returnBegin) { + error("skip, excludeBegin, returnBegin not compatible with beginScope: {}"); + throw MultiClassError; + } + + if (typeof mode.beginScope !== "object" || mode.beginScope === null) { + error("beginScope must be object"); + throw MultiClassError; + } + + remapScopeNames(mode, mode.begin, { key: "beginScope" }); + mode.begin = _rewriteBackreferences(mode.begin, { joinWith: "" }); + } + + /** + * @param {CompiledMode} mode + */ + function endMultiClass(mode) { + if (!Array.isArray(mode.end)) return; + + if (mode.skip || mode.excludeEnd || mode.returnEnd) { + error("skip, excludeEnd, returnEnd not compatible with endScope: {}"); + throw MultiClassError; + } + + if (typeof mode.endScope !== "object" || mode.endScope === null) { + error("endScope must be object"); + throw MultiClassError; + } + + remapScopeNames(mode, mode.end, { key: "endScope" }); + mode.end = _rewriteBackreferences(mode.end, { joinWith: "" }); + } + + /** + * this exists only to allow `scope: {}` to be used beside `match:` + * Otherwise `beginScope` would necessary and that would look weird + + { + match: [ /def/, /\w+/ ] + scope: { 1: "keyword" , 2: "title" } + } + + * @param {CompiledMode} mode + */ + function scopeSugar(mode) { + if (mode.scope && typeof mode.scope === "object" && mode.scope !== null) { + mode.beginScope = mode.scope; + delete mode.scope; + } + } + + /** + * @param {CompiledMode} mode + */ + function MultiClass(mode) { + scopeSugar(mode); + + if (typeof mode.beginScope === "string") { + mode.beginScope = { _wrap: mode.beginScope }; + } + if (typeof mode.endScope === "string") { + mode.endScope = { _wrap: mode.endScope }; + } + + beginMultiClass(mode); + endMultiClass(mode); + } + + /** + @typedef {import('highlight.js').Mode} Mode + @typedef {import('highlight.js').CompiledMode} CompiledMode + @typedef {import('highlight.js').Language} Language + @typedef {import('highlight.js').HLJSPlugin} HLJSPlugin + @typedef {import('highlight.js').CompiledLanguage} CompiledLanguage + */ + + // compilation + + /** + * Compiles a language definition result + * + * Given the raw result of a language definition (Language), compiles this so + * that it is ready for highlighting code. + * @param {Language} language + * @returns {CompiledLanguage} + */ + function compileLanguage(language) { + /** + * Builds a regex with the case sensitivity of the current language + * + * @param {RegExp | string} value + * @param {boolean} [global] + */ + function langRe(value, global) { + return new RegExp( + source(value), + 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '') + ); + } + + /** + Stores multiple regular expressions and allows you to quickly search for + them all in a string simultaneously - returning the first match. It does + this by creating a huge (a|b|c) regex - each individual item wrapped with () + and joined by `|` - using match groups to track position. When a match is + found checking which position in the array has content allows us to figure + out which of the original regexes / match groups triggered the match. + + The match object itself (the result of `Regex.exec`) is returned but also + enhanced by merging in any meta-data that was registered with the regex. + This is how we keep track of which mode matched, and what type of rule + (`illegal`, `begin`, end, etc). + */ + class MultiRegex { + constructor() { + this.matchIndexes = {}; + // @ts-ignore + this.regexes = []; + this.matchAt = 1; + this.position = 0; + } + + // @ts-ignore + addRule(re, opts) { + opts.position = this.position++; + // @ts-ignore + this.matchIndexes[this.matchAt] = opts; + this.regexes.push([opts, re]); + this.matchAt += countMatchGroups(re) + 1; + } + + compile() { + if (this.regexes.length === 0) { + // avoids the need to check length every time exec is called + // @ts-ignore + this.exec = () => null; + } + const terminators = this.regexes.map(el => el[1]); + this.matcherRe = langRe(_rewriteBackreferences(terminators, { joinWith: '|' }), true); + this.lastIndex = 0; + } + + /** @param {string} s */ + exec(s) { + this.matcherRe.lastIndex = this.lastIndex; + const match = this.matcherRe.exec(s); + if (!match) { return null; } + + // eslint-disable-next-line no-undefined + const i = match.findIndex((el, i) => i > 0 && el !== undefined); + // @ts-ignore + const matchData = this.matchIndexes[i]; + // trim off any earlier non-relevant match groups (ie, the other regex + // match groups that make up the multi-matcher) + match.splice(0, i); + + return Object.assign(match, matchData); + } + } + + /* + Created to solve the key deficiently with MultiRegex - there is no way to + test for multiple matches at a single location. Why would we need to do + that? In the future a more dynamic engine will allow certain matches to be + ignored. An example: if we matched say the 3rd regex in a large group but + decided to ignore it - we'd need to started testing again at the 4th + regex... but MultiRegex itself gives us no real way to do that. + + So what this class creates MultiRegexs on the fly for whatever search + position they are needed. + + NOTE: These additional MultiRegex objects are created dynamically. For most + grammars most of the time we will never actually need anything more than the + first MultiRegex - so this shouldn't have too much overhead. + + Say this is our search group, and we match regex3, but wish to ignore it. + + regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0 + + What we need is a new MultiRegex that only includes the remaining + possibilities: + + regex4 | regex5 ' ie, startAt = 3 + + This class wraps all that complexity up in a simple API... `startAt` decides + where in the array of expressions to start doing the matching. It + auto-increments, so if a match is found at position 2, then startAt will be + set to 3. If the end is reached startAt will return to 0. + + MOST of the time the parser will be setting startAt manually to 0. + */ + class ResumableMultiRegex { + constructor() { + // @ts-ignore + this.rules = []; + // @ts-ignore + this.multiRegexes = []; + this.count = 0; + + this.lastIndex = 0; + this.regexIndex = 0; + } + + // @ts-ignore + getMatcher(index) { + if (this.multiRegexes[index]) return this.multiRegexes[index]; + + const matcher = new MultiRegex(); + this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts)); + matcher.compile(); + this.multiRegexes[index] = matcher; + return matcher; + } + + resumingScanAtSamePosition() { + return this.regexIndex !== 0; + } + + considerAll() { + this.regexIndex = 0; + } + + // @ts-ignore + addRule(re, opts) { + this.rules.push([re, opts]); + if (opts.type === "begin") this.count++; + } + + /** @param {string} s */ + exec(s) { + const m = this.getMatcher(this.regexIndex); + m.lastIndex = this.lastIndex; + let result = m.exec(s); + + // The following is because we have no easy way to say "resume scanning at the + // existing position but also skip the current rule ONLY". What happens is + // all prior rules are also skipped which can result in matching the wrong + // thing. Example of matching "booger": + + // our matcher is [string, "booger", number] + // + // ....booger.... + + // if "booger" is ignored then we'd really need a regex to scan from the + // SAME position for only: [string, number] but ignoring "booger" (if it + // was the first match), a simple resume would scan ahead who knows how + // far looking only for "number", ignoring potential string matches (or + // future "booger" matches that might be valid.) + + // So what we do: We execute two matchers, one resuming at the same + // position, but the second full matcher starting at the position after: + + // /--- resume first regex match here (for [number]) + // |/---- full match here for [string, "booger", number] + // vv + // ....booger.... + + // Which ever results in a match first is then used. So this 3-4 step + // process essentially allows us to say "match at this position, excluding + // a prior rule that was ignored". + // + // 1. Match "booger" first, ignore. Also proves that [string] does non match. + // 2. Resume matching for [number] + // 3. Match at index + 1 for [string, "booger", number] + // 4. If #2 and #3 result in matches, which came first? + if (this.resumingScanAtSamePosition()) { + if (result && result.index === this.lastIndex); else { // use the second matcher result + const m2 = this.getMatcher(0); + m2.lastIndex = this.lastIndex + 1; + result = m2.exec(s); + } + } + + if (result) { + this.regexIndex += result.position + 1; + if (this.regexIndex === this.count) { + // wrap-around to considering all matches again + this.considerAll(); + } + } + + return result; + } + } + + /** + * Given a mode, builds a huge ResumableMultiRegex that can be used to walk + * the content and find matches. + * + * @param {CompiledMode} mode + * @returns {ResumableMultiRegex} + */ + function buildModeRegex(mode) { + const mm = new ResumableMultiRegex(); + + mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" })); + + if (mode.terminatorEnd) { + mm.addRule(mode.terminatorEnd, { type: "end" }); + } + if (mode.illegal) { + mm.addRule(mode.illegal, { type: "illegal" }); + } + + return mm; + } + + /** skip vs abort vs ignore + * + * @skip - The mode is still entered and exited normally (and contains rules apply), + * but all content is held and added to the parent buffer rather than being + * output when the mode ends. Mostly used with `sublanguage` to build up + * a single large buffer than can be parsed by sublanguage. + * + * - The mode begin ands ends normally. + * - Content matched is added to the parent mode buffer. + * - The parser cursor is moved forward normally. + * + * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it + * never matched) but DOES NOT continue to match subsequent `contains` + * modes. Abort is bad/suboptimal because it can result in modes + * farther down not getting applied because an earlier rule eats the + * content but then aborts. + * + * - The mode does not begin. + * - Content matched by `begin` is added to the mode buffer. + * - The parser cursor is moved forward accordingly. + * + * @ignore - Ignores the mode (as if it never matched) and continues to match any + * subsequent `contains` modes. Ignore isn't technically possible with + * the current parser implementation. + * + * - The mode does not begin. + * - Content matched by `begin` is ignored. + * - The parser cursor is not moved forward. + */ + + /** + * Compiles an individual mode + * + * This can raise an error if the mode contains certain detectable known logic + * issues. + * @param {Mode} mode + * @param {CompiledMode | null} [parent] + * @returns {CompiledMode | never} + */ + function compileMode(mode, parent) { + const cmode = /** @type CompiledMode */ (mode); + if (mode.isCompiled) return cmode; + + [ + scopeClassName, + // do this early so compiler extensions generally don't have to worry about + // the distinction between match/begin + compileMatch, + MultiClass, + beforeMatchExt + ].forEach(ext => ext(mode, parent)); + + language.compilerExtensions.forEach(ext => ext(mode, parent)); + + // __beforeBegin is considered private API, internal use only + mode.__beforeBegin = null; + + [ + beginKeywords, + // do this later so compiler extensions that come earlier have access to the + // raw array if they wanted to perhaps manipulate it, etc. + compileIllegal, + // default to 1 relevance if not specified + compileRelevance + ].forEach(ext => ext(mode, parent)); + + mode.isCompiled = true; + + let keywordPattern = null; + if (typeof mode.keywords === "object" && mode.keywords.$pattern) { + // we need a copy because keywords might be compiled multiple times + // so we can't go deleting $pattern from the original on the first + // pass + mode.keywords = Object.assign({}, mode.keywords); + keywordPattern = mode.keywords.$pattern; + delete mode.keywords.$pattern; + } + keywordPattern = keywordPattern || /\w+/; + + if (mode.keywords) { + mode.keywords = compileKeywords(mode.keywords, language.case_insensitive); + } + + cmode.keywordPatternRe = langRe(keywordPattern, true); + + if (parent) { + if (!mode.begin) mode.begin = /\B|\b/; + cmode.beginRe = langRe(mode.begin); + if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/; + if (mode.end) cmode.endRe = langRe(mode.end); + cmode.terminatorEnd = source(mode.end) || ''; + if (mode.endsWithParent && parent.terminatorEnd) { + cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd; + } + } + if (mode.illegal) cmode.illegalRe = langRe(/** @type {RegExp | string} */(mode.illegal)); + if (!mode.contains) mode.contains = []; + + mode.contains = [].concat(...mode.contains.map(function (c) { + return expandOrCloneMode(c === 'self' ? mode : c); + })); + mode.contains.forEach(function (c) { compileMode(/** @type Mode */(c), cmode); }); + + if (mode.starts) { + compileMode(mode.starts, parent); + } + + cmode.matcher = buildModeRegex(cmode); + return cmode; + } + + if (!language.compilerExtensions) language.compilerExtensions = []; + + // self is not valid at the top-level + if (language.contains && language.contains.includes('self')) { + throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation."); + } + + // we need a null object, which inherit will guarantee + language.classNameAliases = inherit$1(language.classNameAliases || {}); + + return compileMode(/** @type Mode */(language)); + } + + /** + * Determines if a mode has a dependency on it's parent or not + * + * If a mode does have a parent dependency then often we need to clone it if + * it's used in multiple places so that each copy points to the correct parent, + * where-as modes without a parent can often safely be re-used at the bottom of + * a mode chain. + * + * @param {Mode | null} mode + * @returns {boolean} - is there a dependency on the parent? + * */ + function dependencyOnParent(mode) { + if (!mode) return false; + + return mode.endsWithParent || dependencyOnParent(mode.starts); + } + + /** + * Expands a mode or clones it if necessary + * + * This is necessary for modes with parental dependenceis (see notes on + * `dependencyOnParent`) and for nodes that have `variants` - which must then be + * exploded into their own individual modes at compile time. + * + * @param {Mode} mode + * @returns {Mode | Mode[]} + * */ + function expandOrCloneMode(mode) { + if (mode.variants && !mode.cachedVariants) { + mode.cachedVariants = mode.variants.map(function (variant) { + return inherit$1(mode, { variants: null }, variant); + }); + } + + // EXPAND + // if we have variants then essentially "replace" the mode with the variants + // this happens in compileMode, where this function is called from + if (mode.cachedVariants) { + return mode.cachedVariants; + } + + // CLONE + // if we have dependencies on parents then we need a unique + // instance of ourselves, so we can be reused with many + // different parents without issue + if (dependencyOnParent(mode)) { + return inherit$1(mode, { starts: mode.starts ? inherit$1(mode.starts) : null }); + } + + if (Object.isFrozen(mode)) { + return inherit$1(mode); + } + + // no special dependency issues, just return ourselves + return mode; + } + + var version = "11.0.0-beta1"; + + /* + Syntax highlighting with language autodetection. + https://highlightjs.org/ + */ + + /** + @typedef {import('highlight.js').Mode} Mode + @typedef {import('highlight.js').CompiledMode} CompiledMode + @typedef {import('highlight.js').Language} Language + @typedef {import('highlight.js').HLJSApi} HLJSApi + @typedef {import('highlight.js').HLJSPlugin} HLJSPlugin + @typedef {import('highlight.js').PluginEvent} PluginEvent + @typedef {import('highlight.js').HLJSOptions} HLJSOptions + @typedef {import('highlight.js').LanguageFn} LanguageFn + @typedef {import('highlight.js').HighlightedHTMLElement} HighlightedHTMLElement + @typedef {import('highlight.js').BeforeHighlightContext} BeforeHighlightContext + @typedef {import('highlight.js/private').MatchType} MatchType + @typedef {import('highlight.js/private').KeywordData} KeywordData + @typedef {import('highlight.js/private').EnhancedMatch} EnhancedMatch + @typedef {import('highlight.js/private').AnnotatedError} AnnotatedError + @typedef {import('highlight.js').AutoHighlightResult} AutoHighlightResult + @typedef {import('highlight.js').HighlightOptions} HighlightOptions + @typedef {import('highlight.js').HighlightResult} HighlightResult + */ + + + const escape = escapeHTML; + const inherit = inherit$1; + const NO_MATCH = Symbol("nomatch"); + const MAX_KEYWORD_HITS = 7; + + /** + * @param {any} hljs - object that is extended (legacy) + * @returns {HLJSApi} + */ + const HLJS = function (hljs) { + // Global internal variables used within the highlight.js library. + /** @type {Record} */ + const languages = Object.create(null); + /** @type {Record} */ + const aliases = Object.create(null); + /** @type {HLJSPlugin[]} */ + const plugins = []; + + // safe/production mode - swallows more errors, tries to keep running + // even if a single syntax or parse hits a fatal error + let SAFE_MODE = true; + const LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?"; + /** @type {Language} */ + const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] }; + + // Global options used when within external APIs. This is modified when + // calling the `hljs.configure` function. + /** @type HLJSOptions */ + let options = { + ignoreUnescapedHTML: false, + noHighlightRe: /^(no-?highlight)$/i, + languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, + classPrefix: 'hljs-', + cssSelector: 'pre code', + languages: null, + // beta configuration options, subject to change, welcome to discuss + // https://github.com/highlightjs/highlight.js/issues/1086 + __emitter: TokenTreeEmitter + }; + + /* Utility functions */ + + /** + * Tests a language name to see if highlighting should be skipped + * @param {string} languageName + */ + function shouldNotHighlight(languageName) { + return options.noHighlightRe.test(languageName); + } + + /** + * @param {HighlightedHTMLElement} block - the HTML element to determine language for + */ + function blockLanguage(block) { + let classes = block.className + ' '; + + classes += block.parentNode ? block.parentNode.className : ''; + + // language-* takes precedence over non-prefixed class names. + const match = options.languageDetectRe.exec(classes); + if (match) { + const language = getLanguage(match[1]); + if (!language) { + warn(LANGUAGE_NOT_FOUND.replace("{}", match[1])); + warn("Falling back to no-highlight mode for this block.", block); + } + return language ? match[1] : 'no-highlight'; + } + + return classes + .split(/\s+/) + .find((_class) => shouldNotHighlight(_class) || getLanguage(_class)); + } + + /** + * Core highlighting function. + * + * OLD API + * highlight(lang, code, ignoreIllegals, continuation) + * + * NEW API + * highlight(code, {lang, ignoreIllegals}) + * + * @param {string} codeOrLanguageName - the language to use for highlighting + * @param {string | HighlightOptions} optionsOrCode - the code to highlight + * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail + * @param {CompiledMode} [continuation] - current continuation mode, if any + * + * @returns {HighlightResult} Result - an object that represents the result + * @property {string} language - the language name + * @property {number} relevance - the relevance score + * @property {string} value - the highlighted HTML code + * @property {string} code - the original raw code + * @property {CompiledMode} top - top of the current mode stack + * @property {boolean} illegal - indicates whether any illegal matches were found + */ + function highlight(codeOrLanguageName, optionsOrCode, ignoreIllegals, continuation) { + let code = ""; + let languageName = ""; + if (typeof optionsOrCode === "object") { + code = codeOrLanguageName; + ignoreIllegals = optionsOrCode.ignoreIllegals; + languageName = optionsOrCode.language; + // continuation not supported at all via the new API + // eslint-disable-next-line no-undefined + continuation = undefined; + } else { + // old API + deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated."); + deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"); + languageName = codeOrLanguageName; + code = optionsOrCode; + } + + // https://github.com/highlightjs/highlight.js/issues/3149 + // eslint-disable-next-line no-undefined + if (ignoreIllegals === undefined) { ignoreIllegals = true; } + + /** @type {BeforeHighlightContext} */ + const context = { + code, + language: languageName + }; + // the plugin can change the desired language or the code to be highlighted + // just be changing the object it was passed + fire("before:highlight", context); + + // a before plugin can usurp the result completely by providing it's own + // in which case we don't even need to call highlight + const result = context.result + ? context.result + : _highlight(context.language, context.code, ignoreIllegals, continuation); + + result.code = context.code; + // the plugin can change anything in result to suite it + fire("after:highlight", result); + + return result; + } + + /** + * private highlight that's used internally and does not fire callbacks + * + * @param {string} languageName - the language to use for highlighting + * @param {string} codeToHighlight - the code to highlight + * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail + * @param {CompiledMode?} [continuation] - current continuation mode, if any + * @returns {HighlightResult} - result of the highlight operation + */ + function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) { + const keywordHits = Object.create(null); + + /** + * Return keyword data if a match is a keyword + * @param {CompiledMode} mode - current mode + * @param {string} matchText - the textual match + * @returns {KeywordData | false} + */ + function keywordData(mode, matchText) { + return mode.keywords[matchText]; + } + + function processKeywords() { + if (!top.keywords) { + emitter.addText(modeBuffer); + return; + } + + let lastIndex = 0; + top.keywordPatternRe.lastIndex = 0; + let match = top.keywordPatternRe.exec(modeBuffer); + let buf = ""; + + while (match) { + buf += modeBuffer.substring(lastIndex, match.index); + const word = language.case_insensitive ? match[0].toLowerCase() : match[0]; + const data = keywordData(top, word); + if (data) { + const [kind, keywordRelevance] = data; + emitter.addText(buf); + buf = ""; + + keywordHits[word] = (keywordHits[word] || 0) + 1; + if (keywordHits[word] <= MAX_KEYWORD_HITS) relevance += keywordRelevance; + if (kind.startsWith("_")) { + // _ implied for relevance only, do not highlight + // by applying a class name + buf += match[0]; + } else { + const cssClass = language.classNameAliases[kind] || kind; + emitter.addKeyword(match[0], cssClass); + } + } else { + buf += match[0]; + } + lastIndex = top.keywordPatternRe.lastIndex; + match = top.keywordPatternRe.exec(modeBuffer); + } + buf += modeBuffer.substr(lastIndex); + emitter.addText(buf); + } + + function processSubLanguage() { + if (modeBuffer === "") return; + /** @type HighlightResult */ + let result = null; + + if (typeof top.subLanguage === 'string') { + if (!languages[top.subLanguage]) { + emitter.addText(modeBuffer); + return; + } + result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]); + continuations[top.subLanguage] = /** @type {CompiledMode} */ (result._top); + } else { + result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null); + } + + // Counting embedded language score towards the host language may be disabled + // with zeroing the containing mode relevance. Use case in point is Markdown that + // allows XML everywhere and makes every XML snippet to have a much larger Markdown + // score. + if (top.relevance > 0) { + relevance += result.relevance; + } + emitter.addSublanguage(result._emitter, result.language); + } + + function processBuffer() { + if (top.subLanguage != null) { + processSubLanguage(); + } else { + processKeywords(); + } + modeBuffer = ''; + } + + /** + * @param {CompiledMode} mode + * @param {RegExpMatchArray} match + */ + function emitMultiClass(scope, match) { + let i = 1; + // eslint-disable-next-line no-undefined + while (match[i] !== undefined) { + if (!scope._emit[i]) { i++; continue; } + const klass = language.classNameAliases[scope[i]] || scope[i]; + const text = match[i]; + if (klass) { + emitter.addKeyword(text, klass); + } else { + modeBuffer = text; + processKeywords(); + modeBuffer = ""; + } + i++; + } + } + + /** + * @param {CompiledMode} mode - new mode to start + * @param {RegExpMatchArray} match + */ + function startNewMode(mode, match) { + if (mode.scope && typeof mode.scope === "string") { + emitter.openNode(language.classNameAliases[mode.scope] || mode.scope); + } + if (mode.beginScope) { + // beginScope just wraps the begin match itself in a scope + if (mode.beginScope._wrap) { + emitter.addKeyword(modeBuffer, language.classNameAliases[mode.beginScope._wrap] || mode.beginScope._wrap); + modeBuffer = ""; + } else if (mode.beginScope._multi) { + // at this point modeBuffer should just be the match + emitMultiClass(mode.beginScope, match); + modeBuffer = ""; + } + } + + top = Object.create(mode, { parent: { value: top } }); + return top; + } + + /** + * @param {CompiledMode } mode - the mode to potentially end + * @param {RegExpMatchArray} match - the latest match + * @param {string} matchPlusRemainder - match plus remainder of content + * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode + */ + function endOfMode(mode, match, matchPlusRemainder) { + let matched = startsWith(mode.endRe, matchPlusRemainder); + + if (matched) { + if (mode["on:end"]) { + const resp = new Response(mode); + mode["on:end"](match, resp); + if (resp.isMatchIgnored) matched = false; + } + + if (matched) { + while (mode.endsParent && mode.parent) { + mode = mode.parent; + } + return mode; + } + } + // even if on:end fires an `ignore` it's still possible + // that we might trigger the end node because of a parent mode + if (mode.endsWithParent) { + return endOfMode(mode.parent, match, matchPlusRemainder); + } + } + + /** + * Handle matching but then ignoring a sequence of text + * + * @param {string} lexeme - string containing full match text + */ + function doIgnore(lexeme) { + if (top.matcher.regexIndex === 0) { + // no more regexes to potentially match here, so we move the cursor forward one + // space + modeBuffer += lexeme[0]; + return 1; + } else { + // no need to move the cursor, we still have additional regexes to try and + // match at this very spot + resumeScanAtSamePosition = true; + return 0; + } + } + + /** + * Handle the start of a new potential mode match + * + * @param {EnhancedMatch} match - the current match + * @returns {number} how far to advance the parse cursor + */ + function doBeginMatch(match) { + const lexeme = match[0]; + const newMode = match.rule; + + const resp = new Response(newMode); + // first internal before callbacks, then the public ones + const beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]]; + for (const cb of beforeCallbacks) { + if (!cb) continue; + cb(match, resp); + if (resp.isMatchIgnored) return doIgnore(lexeme); + } + + if (newMode.skip) { + modeBuffer += lexeme; + } else { + if (newMode.excludeBegin) { + modeBuffer += lexeme; + } + processBuffer(); + if (!newMode.returnBegin && !newMode.excludeBegin) { + modeBuffer = lexeme; + } + } + startNewMode(newMode, match); + return newMode.returnBegin ? 0 : lexeme.length; + } + + /** + * Handle the potential end of mode + * + * @param {RegExpMatchArray} match - the current match + */ + function doEndMatch(match) { + const lexeme = match[0]; + const matchPlusRemainder = codeToHighlight.substr(match.index); + + const endMode = endOfMode(top, match, matchPlusRemainder); + if (!endMode) { return NO_MATCH; } + + const origin = top; + if (top.endScope && top.endScope._wrap) { + processBuffer(); + emitter.addKeyword(lexeme, top.endScope._wrap); + } else if (top.endScope && top.endScope._multi) { + processBuffer(); + emitMultiClass(top.endScope, match); + } else if (origin.skip) { + modeBuffer += lexeme; + } else { + if (!(origin.returnEnd || origin.excludeEnd)) { + modeBuffer += lexeme; + } + processBuffer(); + if (origin.excludeEnd) { + modeBuffer = lexeme; + } + } + do { + if (top.scope && !top.isMultiClass) { + emitter.closeNode(); + } + if (!top.skip && !top.subLanguage) { + relevance += top.relevance; + } + top = top.parent; + } while (top !== endMode.parent); + if (endMode.starts) { + startNewMode(endMode.starts, match); + } + return origin.returnEnd ? 0 : lexeme.length; + } + + function processContinuations() { + const list = []; + for (let current = top; current !== language; current = current.parent) { + if (current.scope) { + list.unshift(current.scope); + } + } + list.forEach(item => emitter.openNode(item)); + } + + /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ + let lastMatch = {}; + + /** + * Process an individual match + * + * @param {string} textBeforeMatch - text preceding the match (since the last match) + * @param {EnhancedMatch} [match] - the match itself + */ + function processLexeme(textBeforeMatch, match) { + const lexeme = match && match[0]; + + // add non-matched text to the current mode buffer + modeBuffer += textBeforeMatch; + + if (lexeme == null) { + processBuffer(); + return 0; + } + + // we've found a 0 width match and we're stuck, so we need to advance + // this happens when we have badly behaved rules that have optional matchers to the degree that + // sometimes they can end up matching nothing at all + // Ref: https://github.com/highlightjs/highlight.js/issues/2140 + if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") { + // spit the "skipped" character that our regex choked on back into the output sequence + modeBuffer += codeToHighlight.slice(match.index, match.index + 1); + if (!SAFE_MODE) { + /** @type {AnnotatedError} */ + const err = new Error(`0 width match regex (${languageName})`); + err.languageName = languageName; + err.badRule = lastMatch.rule; + throw err; + } + return 1; + } + lastMatch = match; + + if (match.type === "begin") { + return doBeginMatch(match); + } else if (match.type === "illegal" && !ignoreIllegals) { + // illegal match, we do not continue processing + /** @type {AnnotatedError} */ + const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.scope || '') + '"'); + err.mode = top; + throw err; + } else if (match.type === "end") { + const processed = doEndMatch(match); + if (processed !== NO_MATCH) { + return processed; + } + } + + // edge case for when illegal matches $ (end of line) which is technically + // a 0 width match but not a begin/end match so it's not caught by the + // first handler (when ignoreIllegals is true) + if (match.type === "illegal" && lexeme === "") { + // advance so we aren't stuck in an infinite loop + return 1; + } + + // infinite loops are BAD, this is a last ditch catch all. if we have a + // decent number of iterations yet our index (cursor position in our + // parsing) still 3x behind our index then something is very wrong + // so we bail + if (iterations > 100000 && iterations > match.index * 3) { + const err = new Error('potential infinite loop, way more iterations than matches'); + throw err; + } + + /* + Why might be find ourselves here? An potential end match that was + triggered but could not be completed. IE, `doEndMatch` returned NO_MATCH. + (this could be because a callback requests the match be ignored, etc) + + This causes no real harm other than stopping a few times too many. + */ + + modeBuffer += lexeme; + return lexeme.length; + } + + const language = getLanguage(languageName); + if (!language) { + error(LANGUAGE_NOT_FOUND.replace("{}", languageName)); + throw new Error('Unknown language: "' + languageName + '"'); + } + + const md = compileLanguage(language); + let result = ''; + /** @type {CompiledMode} */ + let top = continuation || md; + /** @type Record */ + const continuations = {}; // keep continuations for sub-languages + const emitter = new options.__emitter(options); + processContinuations(); + let modeBuffer = ''; + let relevance = 0; + let index = 0; + let iterations = 0; + let resumeScanAtSamePosition = false; + + try { + top.matcher.considerAll(); + + for (; ;) { + iterations++; + if (resumeScanAtSamePosition) { + // only regexes not matched previously will now be + // considered for a potential match + resumeScanAtSamePosition = false; + } else { + top.matcher.considerAll(); + } + top.matcher.lastIndex = index; + + const match = top.matcher.exec(codeToHighlight); + // console.log("match", match[0], match.rule && match.rule.begin) + + if (!match) break; + + const beforeMatch = codeToHighlight.substring(index, match.index); + const processedCount = processLexeme(beforeMatch, match); + index = match.index + processedCount; + } + processLexeme(codeToHighlight.substr(index)); + emitter.closeAllNodes(); + emitter.finalize(); + result = emitter.toHTML(); + + return { + language: languageName, + value: result, + relevance: relevance, + illegal: false, + _emitter: emitter, + _top: top + }; + } catch (err) { + if (err.message && err.message.includes('Illegal')) { + return { + language: languageName, + value: escape(codeToHighlight), + illegal: true, + relevance: 0, + _illegalBy: { + message: err.message, + index: index, + context: codeToHighlight.slice(index - 100, index + 100), + mode: err.mode, + resultSoFar: result + }, + _emitter: emitter + }; + } else if (SAFE_MODE) { + return { + language: languageName, + value: escape(codeToHighlight), + illegal: false, + relevance: 0, + errorRaised: err, + _emitter: emitter, + _top: top + }; + } else { + throw err; + } + } + } + + /** + * returns a valid highlight result, without actually doing any actual work, + * auto highlight starts with this and it's possible for small snippets that + * auto-detection may not find a better match + * @param {string} code + * @returns {HighlightResult} + */ + function justTextHighlightResult(code) { + const result = { + value: escape(code), + illegal: false, + relevance: 0, + _top: PLAINTEXT_LANGUAGE, + _emitter: new options.__emitter(options) + }; + result._emitter.addText(code); + return result; + } + + /** + Highlighting with language detection. Accepts a string with the code to + highlight. Returns an object with the following properties: + + - language (detected language) + - relevance (int) + - value (an HTML string with highlighting markup) + - secondBest (object with the same structure for second-best heuristically + detected language, may be absent) + + @param {string} code + @param {Array} [languageSubset] + @returns {AutoHighlightResult} + */ + function highlightAuto(code, languageSubset) { + languageSubset = languageSubset || options.languages || Object.keys(languages); + const plaintext = justTextHighlightResult(code); + + const results = languageSubset.filter(getLanguage).filter(autoDetection).map(name => + _highlight(name, code, false) + ); + results.unshift(plaintext); // plaintext is always an option + + const sorted = results.sort((a, b) => { + // sort base on relevance + if (a.relevance !== b.relevance) return b.relevance - a.relevance; + + // always award the tie to the base language + // ie if C++ and Arduino are tied, it's more likely to be C++ + if (a.language && b.language) { + if (getLanguage(a.language).supersetOf === b.language) { + return 1; + } else if (getLanguage(b.language).supersetOf === a.language) { + return -1; + } + } + + // otherwise say they are equal, which has the effect of sorting on + // relevance while preserving the original ordering - which is how ties + // have historically been settled, ie the language that comes first always + // wins in the case of a tie + return 0; + }); + + const [best, secondBest] = sorted; + + /** @type {AutoHighlightResult} */ + const result = best; + result.secondBest = secondBest; + + return result; + } + + /** + * Builds new class name for block given the language name + * + * @param {HTMLElement} element + * @param {string} [currentLang] + * @param {string} [resultLang] + */ + function updateClassName(element, currentLang, resultLang) { + const language = (currentLang && aliases[currentLang]) || resultLang; + + element.classList.add("hljs"); + element.classList.add(`language-${language}`); + } + + /** + * Applies highlighting to a DOM node containing code. + * + * @param {HighlightedHTMLElement} element - the HTML element to highlight + */ + function highlightElement(element) { + /** @type HTMLElement */ + let node = null; + const language = blockLanguage(element); + + if (shouldNotHighlight(language)) return; + + fire("before:highlightElement", + { el: element, language: language }); + + // we should be all text, no child nodes + if (!options.ignoreUnescapedHTML && element.children.length > 0) { + console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."); + console.warn("https://github.com/highlightjs/highlight.js/issues/2886"); + console.warn(element); + } + + node = element; + const text = node.textContent; + const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text); + + fire("after:highlightElement", { el: element, result, text }); + + element.innerHTML = result.value; + updateClassName(element, language, result.language); + element.result = { + language: result.language, + // TODO: remove with version 11.0 + re: result.relevance, + relevance: result.relevance + }; + if (result.secondBest) { + element.secondBest = { + language: result.secondBest.language, + relevance: result.secondBest.relevance + }; + } + } + + /** + * Updates highlight.js global options with the passed options + * + * @param {Partial} userOptions + */ + function configure(userOptions) { + options = inherit(options, userOptions); + } + + // TODO: remove v12, deprecated + const initHighlighting = () => { + highlightAll(); + deprecated("10.6.0", "initHighlighting() deprecated. Use highlightAll() now."); + }; + + // TODO: remove v12, deprecated + function initHighlightingOnLoad() { + highlightAll(); + deprecated("10.6.0", "initHighlightingOnLoad() deprecated. Use highlightAll() now."); + } + + let wantsHighlight = false; + + /** + * auto-highlights all pre>code elements on the page + */ + function highlightAll() { + // if we are called too early in the loading process + if (document.readyState === "loading") { + wantsHighlight = true; + return; + } + + const blocks = document.querySelectorAll(options.cssSelector); + blocks.forEach(highlightElement); + } + + function boot() { + // if a highlight was requested before DOM was loaded, do now + if (wantsHighlight) highlightAll(); + } + + // make sure we are in the browser environment + if (typeof window !== 'undefined' && window.addEventListener) { + window.addEventListener('DOMContentLoaded', boot, false); + } + + /** + * Register a language grammar module + * + * @param {string} languageName + * @param {LanguageFn} languageDefinition + */ + function registerLanguage(languageName, languageDefinition) { + let lang = null; + try { + lang = languageDefinition(hljs); + } catch (error$1) { + error("Language definition for '{}' could not be registered.".replace("{}", languageName)); + // hard or soft error + if (!SAFE_MODE) { throw error$1; } else { error(error$1); } + // languages that have serious errors are replaced with essentially a + // "plaintext" stand-in so that the code blocks will still get normal + // css classes applied to them - and one bad language won't break the + // entire highlighter + lang = PLAINTEXT_LANGUAGE; + } + // give it a temporary name if it doesn't have one in the meta-data + if (!lang.name) lang.name = languageName; + languages[languageName] = lang; + lang.rawDefinition = languageDefinition.bind(null, hljs); + + if (lang.aliases) { + registerAliases(lang.aliases, { languageName }); + } + } + + /** + * Remove a language grammar module + * + * @param {string} languageName + */ + function unregisterLanguage(languageName) { + delete languages[languageName]; + for (const alias of Object.keys(aliases)) { + if (aliases[alias] === languageName) { + delete aliases[alias]; + } + } + } + + /** + * @returns {string[]} List of language internal names + */ + function listLanguages() { + return Object.keys(languages); + } + + /** + * @param {string} name - name of the language to retrieve + * @returns {Language | undefined} + */ + function getLanguage(name) { + name = (name || '').toLowerCase(); + return languages[name] || languages[aliases[name]]; + } + + /** + * + * @param {string|string[]} aliasList - single alias or list of aliases + * @param {{languageName: string}} opts + */ + function registerAliases(aliasList, { languageName }) { + if (typeof aliasList === 'string') { + aliasList = [aliasList]; + } + aliasList.forEach(alias => { aliases[alias.toLowerCase()] = languageName; }); + } + + /** + * Determines if a given language has auto-detection enabled + * @param {string} name - name of the language + */ + function autoDetection(name) { + const lang = getLanguage(name); + return lang && !lang.disableAutodetect; + } + + /** + * Upgrades the old highlightBlock plugins to the new + * highlightElement API + * @param {HLJSPlugin} plugin + */ + function upgradePluginAPI(plugin) { + // TODO: remove with v12 + if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) { + plugin["before:highlightElement"] = (data) => { + plugin["before:highlightBlock"]( + Object.assign({ block: data.el }, data) + ); + }; + } + if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) { + plugin["after:highlightElement"] = (data) => { + plugin["after:highlightBlock"]( + Object.assign({ block: data.el }, data) + ); + }; + } + } + + /** + * @param {HLJSPlugin} plugin + */ + function addPlugin(plugin) { + upgradePluginAPI(plugin); + plugins.push(plugin); + } + + /** + * + * @param {PluginEvent} event + * @param {any} args + */ + function fire(event, args) { + const cb = event; + plugins.forEach(function (plugin) { + if (plugin[cb]) { + plugin[cb](args); + } + }); + } + + /** + * + * @param {HighlightedHTMLElement} el + */ + function deprecateHighlightBlock(el) { + deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0"); + deprecated("10.7.0", "Please use highlightElement now."); + + return highlightElement(el); + } + + /* Interface definition */ + Object.assign(hljs, { + highlight, + highlightAuto, + highlightAll, + highlightElement, + // TODO: Remove with v12 API + highlightBlock: deprecateHighlightBlock, + configure, + initHighlighting, + initHighlightingOnLoad, + registerLanguage, + unregisterLanguage, + listLanguages, + getLanguage, + registerAliases, + autoDetection, + inherit, + addPlugin + }); + + hljs.debugMode = function () { SAFE_MODE = false; }; + hljs.safeMode = function () { SAFE_MODE = true; }; + hljs.versionString = version; + + for (const key in MODES$1) { + // @ts-ignore + if (typeof MODES$1[key] === "object") { + // @ts-ignore + deepFreeze$1(MODES$1[key]); + } + } + + // merge all the modes/regexes into our main object + Object.assign(hljs, MODES$1); + + return hljs; + }; + + // export an "instance" of the highlighter + var HighlightJS = HLJS({}); + + /* + Language: Bash + Author: vah + Contributrors: Benjamin Pannell + Website: https://www.gnu.org/software/bash/ + Category: common + */ + + /** @type LanguageFn */ + function bash(hljs) { + const VAR = {}; + const BRACED_VAR = { + begin: /\$\{/, + end: /\}/, + contains: [ + "self", + { + begin: /:-/, + contains: [VAR] + } // default values + ] + }; + Object.assign(VAR, { + className: 'variable', + variants: [ + { + begin: concat(/\$[\w\d#@][\w\d_]*/, + // negative look-ahead tries to avoid matching patterns that are not + // Perl at all like $ident$, @ident@, etc. + `(?![\\w\\d])(?![$])`) + }, + BRACED_VAR + ] + }); + + const SUBST = { + className: 'subst', + begin: /\$\(/, end: /\)/, + contains: [hljs.BACKSLASH_ESCAPE] + }; + const HERE_DOC = { + begin: /<<-?\s*(?=\w+)/, + starts: { + contains: [ + hljs.END_SAME_AS_BEGIN({ + begin: /(\w+)/, + end: /(\w+)/, + className: 'string' + }) + ] + } + }; + const QUOTE_STRING = { + className: 'string', + begin: /"/, end: /"/, + contains: [ + hljs.BACKSLASH_ESCAPE, + VAR, + SUBST + ] + }; + SUBST.contains.push(QUOTE_STRING); + const ESCAPED_QUOTE = { + className: '', + begin: /\\"/ + + }; + const APOS_STRING = { + className: 'string', + begin: /'/, end: /'/ + }; + const ARITHMETIC = { + begin: /\$\(\(/, + end: /\)\)/, + contains: [ + { begin: /\d+#[0-9a-f]+/, className: "number" }, + hljs.NUMBER_MODE, + VAR + ] + }; + const SH_LIKE_SHELLS = [ + "fish", + "bash", + "zsh", + "sh", + "csh", + "ksh", + "tcsh", + "dash", + "scsh", + ]; + const KNOWN_SHEBANG = hljs.SHEBANG({ + binary: `(${SH_LIKE_SHELLS.join("|")})`, + relevance: 10 + }); + const FUNCTION = { + className: 'function', + begin: /\w[\w\d_]*\s*\(\s*\)\s*\{/, + returnBegin: true, + contains: [hljs.inherit(hljs.TITLE_MODE, { begin: /\w[\w\d_]*/ })], + relevance: 0 + }; + + const KEYWORDS = [ + "if", + "then", + "else", + "elif", + "fi", + "for", + "while", + "in", + "do", + "done", + "case", + "esac", + "function" + ]; + + const LITERALS = [ + "true", + "false" + ]; + + return { + name: 'Bash', + aliases: ['sh'], + keywords: { + $pattern: /\b[a-z._-]+\b/, + keyword: KEYWORDS, + literal: LITERALS, + built_in: + // Shell built-ins + // http://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html + 'break cd continue eval exec exit export getopts hash pwd readonly return shift test times ' + + 'trap umask unset ' + + // Bash built-ins + 'alias bind builtin caller command declare echo enable help let local logout mapfile printf ' + + 'read readarray source type typeset ulimit unalias ' + + // Shell modifiers + 'set shopt ' + + // Zsh built-ins + 'autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles ' + + 'compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate ' + + 'fc fg float functions getcap getln history integer jobs kill limit log noglob popd print ' + + 'pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit ' + + 'unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof ' + + 'zpty zregexparse zsocket zstyle ztcp' + }, + contains: [ + KNOWN_SHEBANG, // to catch known shells and boost relevancy + hljs.SHEBANG(), // to catch unknown shells but still highlight the shebang + FUNCTION, + ARITHMETIC, + hljs.HASH_COMMENT_MODE, + HERE_DOC, + QUOTE_STRING, + ESCAPED_QUOTE, + APOS_STRING, + VAR + ] + }; + } + + + const MODES = (hljs) => { + return { + IMPORTANT: { + scope: 'meta', + begin: '!important' + }, + HEXCOLOR: { + scope: 'number', + begin: '#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})' + }, + ATTRIBUTE_SELECTOR_MODE: { + scope: 'selector-attr', + begin: /\[/, + end: /\]/, + illegal: '$', + contains: [ + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE + ] + }, + CSS_NUMBER_MODE: { + scope: 'number', + begin: hljs.NUMBER_RE + '(' + + '%|em|ex|ch|rem' + + '|vw|vh|vmin|vmax' + + '|cm|mm|in|pt|pc|px' + + '|deg|grad|rad|turn' + + '|s|ms' + + '|Hz|kHz' + + '|dpi|dpcm|dppx' + + ')?', + relevance: 0 + } + }; + }; + + const TAGS = [ + 'a', + 'abbr', + 'address', + 'article', + 'aside', + 'audio', + 'b', + 'blockquote', + 'body', + 'button', + 'canvas', + 'caption', + 'cite', + 'code', + 'dd', + 'del', + 'details', + 'dfn', + 'div', + 'dl', + 'dt', + 'em', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'header', + 'hgroup', + 'html', + 'i', + 'iframe', + 'img', + 'input', + 'ins', + 'kbd', + 'label', + 'legend', + 'li', + 'main', + 'mark', + 'menu', + 'nav', + 'object', + 'ol', + 'p', + 'q', + 'quote', + 'samp', + 'section', + 'span', + 'strong', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'time', + 'tr', + 'ul', + 'var', + 'video' + ]; + + const MEDIA_FEATURES = [ + 'any-hover', + 'any-pointer', + 'aspect-ratio', + 'color', + 'color-gamut', + 'color-index', + 'device-aspect-ratio', + 'device-height', + 'device-width', + 'display-mode', + 'forced-colors', + 'grid', + 'height', + 'hover', + 'inverted-colors', + 'monochrome', + 'orientation', + 'overflow-block', + 'overflow-inline', + 'pointer', + 'prefers-color-scheme', + 'prefers-contrast', + 'prefers-reduced-motion', + 'prefers-reduced-transparency', + 'resolution', + 'scan', + 'scripting', + 'update', + 'width', + // TODO: find a better solution? + 'min-width', + 'max-width', + 'min-height', + 'max-height' + ]; + + // https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes + const PSEUDO_CLASSES = [ + 'active', + 'any-link', + 'blank', + 'checked', + 'current', + 'default', + 'defined', + 'dir', // dir() + 'disabled', + 'drop', + 'empty', + 'enabled', + 'first', + 'first-child', + 'first-of-type', + 'fullscreen', + 'future', + 'focus', + 'focus-visible', + 'focus-within', + 'has', // has() + 'host', // host or host() + 'host-context', // host-context() + 'hover', + 'indeterminate', + 'in-range', + 'invalid', + 'is', // is() + 'lang', // lang() + 'last-child', + 'last-of-type', + 'left', + 'link', + 'local-link', + 'not', // not() + 'nth-child', // nth-child() + 'nth-col', // nth-col() + 'nth-last-child', // nth-last-child() + 'nth-last-col', // nth-last-col() + 'nth-last-of-type', //nth-last-of-type() + 'nth-of-type', //nth-of-type() + 'only-child', + 'only-of-type', + 'optional', + 'out-of-range', + 'past', + 'placeholder-shown', + 'read-only', + 'read-write', + 'required', + 'right', + 'root', + 'scope', + 'target', + 'target-within', + 'user-invalid', + 'valid', + 'visited', + 'where' // where() + ]; + + // https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements + const PSEUDO_ELEMENTS = [ + 'after', + 'backdrop', + 'before', + 'cue', + 'cue-region', + 'first-letter', + 'first-line', + 'grammar-error', + 'marker', + 'part', + 'placeholder', + 'selection', + 'slotted', + 'spelling-error' + ]; + + const ATTRIBUTES = [ + 'align-content', + 'align-items', + 'align-self', + 'animation', + 'animation-delay', + 'animation-direction', + 'animation-duration', + 'animation-fill-mode', + 'animation-iteration-count', + 'animation-name', + 'animation-play-state', + 'animation-timing-function', + 'auto', + 'backface-visibility', + 'background', + 'background-attachment', + 'background-clip', + 'background-color', + 'background-image', + 'background-origin', + 'background-position', + 'background-repeat', + 'background-size', + 'border', + 'border-bottom', + 'border-bottom-color', + 'border-bottom-left-radius', + 'border-bottom-right-radius', + 'border-bottom-style', + 'border-bottom-width', + 'border-collapse', + 'border-color', + 'border-image', + 'border-image-outset', + 'border-image-repeat', + 'border-image-slice', + 'border-image-source', + 'border-image-width', + 'border-left', + 'border-left-color', + 'border-left-style', + 'border-left-width', + 'border-radius', + 'border-right', + 'border-right-color', + 'border-right-style', + 'border-right-width', + 'border-spacing', + 'border-style', + 'border-top', + 'border-top-color', + 'border-top-left-radius', + 'border-top-right-radius', + 'border-top-style', + 'border-top-width', + 'border-width', + 'bottom', + 'box-decoration-break', + 'box-shadow', + 'box-sizing', + 'break-after', + 'break-before', + 'break-inside', + 'caption-side', + 'clear', + 'clip', + 'clip-path', + 'color', + 'column-count', + 'column-fill', + 'column-gap', + 'column-rule', + 'column-rule-color', + 'column-rule-style', + 'column-rule-width', + 'column-span', + 'column-width', + 'columns', + 'content', + 'counter-increment', + 'counter-reset', + 'cursor', + 'direction', + 'display', + 'empty-cells', + 'filter', + 'flex', + 'flex-basis', + 'flex-direction', + 'flex-flow', + 'flex-grow', + 'flex-shrink', + 'flex-wrap', + 'float', + 'font', + 'font-display', + 'font-family', + 'font-feature-settings', + 'font-kerning', + 'font-language-override', + 'font-size', + 'font-size-adjust', + 'font-smoothing', + 'font-stretch', + 'font-style', + 'font-variant', + 'font-variant-ligatures', + 'font-variation-settings', + 'font-weight', + 'height', + 'hyphens', + 'icon', + 'image-orientation', + 'image-rendering', + 'image-resolution', + 'ime-mode', + 'inherit', + 'initial', + 'justify-content', + 'left', + 'letter-spacing', + 'line-height', + 'list-style', + 'list-style-image', + 'list-style-position', + 'list-style-type', + 'margin', + 'margin-bottom', + 'margin-left', + 'margin-right', + 'margin-top', + 'marks', + 'mask', + 'max-height', + 'max-width', + 'min-height', + 'min-width', + 'nav-down', + 'nav-index', + 'nav-left', + 'nav-right', + 'nav-up', + 'none', + 'normal', + 'object-fit', + 'object-position', + 'opacity', + 'order', + 'orphans', + 'outline', + 'outline-color', + 'outline-offset', + 'outline-style', + 'outline-width', + 'overflow', + 'overflow-wrap', + 'overflow-x', + 'overflow-y', + 'padding', + 'padding-bottom', + 'padding-left', + 'padding-right', + 'padding-top', + 'page-break-after', + 'page-break-before', + 'page-break-inside', + 'perspective', + 'perspective-origin', + 'pointer-events', + 'position', + 'quotes', + 'resize', + 'right', + 'src', // @font-face + 'tab-size', + 'table-layout', + 'text-align', + 'text-align-last', + 'text-decoration', + 'text-decoration-color', + 'text-decoration-line', + 'text-decoration-style', + 'text-indent', + 'text-overflow', + 'text-rendering', + 'text-shadow', + 'text-transform', + 'text-underline-position', + 'top', + 'transform', + 'transform-origin', + 'transform-style', + 'transition', + 'transition-delay', + 'transition-duration', + 'transition-property', + 'transition-timing-function', + 'unicode-bidi', + 'vertical-align', + 'visibility', + 'white-space', + 'widows', + 'width', + 'word-break', + 'word-spacing', + 'word-wrap', + 'z-index' + // reverse makes sure longer attributes `font-weight` are matched fully + // instead of getting false positives on say `font` + ].reverse(); + + // some grammars use them all as a single group + const PSEUDO_SELECTORS = PSEUDO_CLASSES.concat(PSEUDO_ELEMENTS); + + + // https://docs.oracle.com/javase/specs/jls/se15/html/jls-3.html#jls-3.10 + var decimalDigits = '[0-9](_*[0-9])*'; + var frac = `\\.(${decimalDigits})`; + var hexDigits = '[0-9a-fA-F](_*[0-9a-fA-F])*'; + var NUMERIC = { + className: 'number', + variants: [ + // DecimalFloatingPointLiteral + // including ExponentPart + { + begin: `(\\b(${decimalDigits})((${frac})|\\.)?|(${frac}))` + + `[eE][+-]?(${decimalDigits})[fFdD]?\\b` + }, + // excluding ExponentPart + { begin: `\\b(${decimalDigits})((${frac})[fFdD]?\\b|\\.([fFdD]\\b)?)` }, + { begin: `(${frac})[fFdD]?\\b` }, + { begin: `\\b(${decimalDigits})[fFdD]\\b` }, + + // HexadecimalFloatingPointLiteral + { + begin: `\\b0[xX]((${hexDigits})\\.?|(${hexDigits})?\\.(${hexDigits}))` + + `[pP][+-]?(${decimalDigits})[fFdD]?\\b` + }, + + // DecimalIntegerLiteral + { begin: '\\b(0|[1-9](_*[0-9])*)[lL]?\\b' }, + + // HexIntegerLiteral + { begin: `\\b0[xX](${hexDigits})[lL]?\\b` }, + + // OctalIntegerLiteral + { begin: '\\b0(_*[0-7])*[lL]?\\b' }, + + // BinaryIntegerLiteral + { begin: '\\b0[bB][01](_*[01])*[lL]?\\b' }, + ], + relevance: 0 + }; + + + /** + * Allows recursive regex expressions to a given depth + * + * ie: recurRegex("(abc~~~)", /~~~/g, 2) becomes: + * (abc(abc(abc))) + * + * @param {string} re + * @param {RegExp} substitution (should be a g mode regex) + * @param {number} depth + * @returns {string}`` + */ + function recurRegex(re, substitution, depth) { + if (depth === -1) return ""; + + return re.replace(substitution, _ => { + return recurRegex(re, substitution, depth - 1); + }); + } + + const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*'; + const KEYWORDS = [ + "as", // for exports + "in", + "of", + "if", + "for", + "while", + "finally", + "var", + "new", + "function", + "do", + "return", + "void", + "else", + "break", + "catch", + "instanceof", + "with", + "throw", + "case", + "default", + "try", + "switch", + "continue", + "typeof", + "delete", + "let", + "yield", + "const", + "class", + // JS handles these with a special rule + // "get", + // "set", + "debugger", + "async", + "await", + "static", + "import", + "from", + "export", + "extends" + ]; + const LITERALS = [ + "true", + "false", + "null", + "undefined", + "NaN", + "Infinity" + ]; + + const TYPES = [ + "Intl", + "DataView", + "Number", + "Math", + "Date", + "String", + "RegExp", + "Object", + "Function", + "Boolean", + "Error", + "Symbol", + "Set", + "Map", + "WeakSet", + "WeakMap", + "Proxy", + "Reflect", + "JSON", + "Promise", + "Float64Array", + "Int16Array", + "Int32Array", + "Int8Array", + "Uint16Array", + "Uint32Array", + "Float32Array", + "Array", + "Uint8Array", + "Uint8ClampedArray", + "ArrayBuffer", + "BigInt64Array", + "BigUint64Array", + "BigInt" + ]; + + const ERROR_TYPES = [ + "EvalError", + "InternalError", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError" + ]; + + const BUILT_IN_GLOBALS = [ + "setInterval", + "setTimeout", + "clearInterval", + "clearTimeout", + + "require", + "exports", + + "eval", + "isFinite", + "isNaN", + "parseFloat", + "parseInt", + "decodeURI", + "decodeURIComponent", + "encodeURI", + "encodeURIComponent", + "escape", + "unescape" + ]; + + const BUILT_IN_VARIABLES = [ + "arguments", + "this", + "super", + "console", + "window", + "document", + "localStorage", + "module", + "global" // Node.js + ]; + + const BUILT_INS = [].concat( + BUILT_IN_GLOBALS, + TYPES, + ERROR_TYPES + ); + + /* + Language: JavaScript + Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions. + Category: common, scripting, web + Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript + */ + + /** @type LanguageFn */ + function javascript(hljs) { + /** + * Takes a string like " { + const tag = "', + end: '' + }; + const XML_TAG = { + begin: /<[A-Za-z0-9\\._:-]+/, + end: /\/[A-Za-z0-9\\._:-]+>|\/>/, + /** + * @param {RegExpMatchArray} match + * @param {CallbackResponse} response + */ + isTrulyOpeningTag: (match, response) => { + const afterMatchIndex = match[0].length + match.index; + const nextChar = match.input[afterMatchIndex]; + // nested type? + // HTML should not include another raw `<` inside a tag + // But a type might: `>`, etc. + if (nextChar === "<") { + response.ignoreMatch(); + return; + } + // + // This is now either a tag or a type. + if (nextChar === ">") { + // if we cannot find a matching closing tag, then we + // will ignore it + if (!hasClosingTag(match, { after: afterMatchIndex })) { + response.ignoreMatch(); + } + } + } + }; + const KEYWORDS$1 = { + $pattern: IDENT_RE, + keyword: KEYWORDS, + literal: LITERALS, + built_in: BUILT_INS, + "variable.language": BUILT_IN_VARIABLES + }; + + // https://tc39.es/ecma262/#sec-literals-numeric-literals + const decimalDigits = '[0-9](_?[0-9])*'; + const frac = `\\.(${decimalDigits})`; + // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral + // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals + const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`; + const NUMBER = { + className: 'number', + variants: [ + // DecimalLiteral + { + begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))` + + `[eE][+-]?(${decimalDigits})\\b` + }, + { begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` }, + + // DecimalBigIntegerLiteral + { begin: `\\b(0|[1-9](_?[0-9])*)n\\b` }, + + // NonDecimalIntegerLiteral + { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" }, + { begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" }, + { begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" }, + + // LegacyOctalIntegerLiteral (does not include underscore separators) + // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals + { begin: "\\b0[0-7]+n?\\b" }, + ], + relevance: 0 + }; + + const SUBST = { + className: 'subst', + begin: '\\$\\{', + end: '\\}', + keywords: KEYWORDS$1, + contains: [] // defined later + }; + const HTML_TEMPLATE = { + begin: 'html`', + end: '', + starts: { + end: '`', + returnEnd: false, + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ], + subLanguage: 'xml' + } + }; + const CSS_TEMPLATE = { + begin: 'css`', + end: '', + starts: { + end: '`', + returnEnd: false, + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ], + subLanguage: 'css' + } + }; + const TEMPLATE_STRING = { + className: 'string', + begin: '`', + end: '`', + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ] + }; + const JSDOC_COMMENT = hljs.COMMENT( + /\/\*\*(?!\/)/, + '\\*/', + { + relevance: 0, + contains: [ + { + begin: '(?=@[A-Za-z]+)', + relevance: 0, + contains: [ + { + className: 'doctag', + begin: '@[A-Za-z]+' + }, + { + className: 'type', + begin: '\\{', + end: '\\}', + excludeEnd: true, + excludeBegin: true, + relevance: 0 + }, + { + className: 'variable', + begin: IDENT_RE$1 + '(?=\\s*(-)|$)', + endsParent: true, + relevance: 0 + }, + // eat spaces (not newlines) so we can find + // types or variables + { + begin: /(?=[^\n])\s/, + relevance: 0 + } + ] + } + ] + } + ); + const COMMENT = { + className: "comment", + variants: [ + JSDOC_COMMENT, + hljs.C_BLOCK_COMMENT_MODE, + hljs.C_LINE_COMMENT_MODE + ] + }; + const SUBST_INTERNALS = [ + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + HTML_TEMPLATE, + CSS_TEMPLATE, + TEMPLATE_STRING, + NUMBER, + hljs.REGEXP_MODE + ]; + SUBST.contains = SUBST_INTERNALS + .concat({ + // we need to pair up {} inside our subst to prevent + // it from ending too early by matching another } + begin: /\{/, + end: /\}/, + keywords: KEYWORDS$1, + contains: [ + "self" + ].concat(SUBST_INTERNALS) + }); + const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains); + const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([ + // eat recursive parens in sub expressions + { + begin: /\(/, + end: /\)/, + keywords: KEYWORDS$1, + contains: ["self"].concat(SUBST_AND_COMMENTS) + } + ]); + const PARAMS = { + className: 'params', + begin: /\(/, + end: /\)/, + excludeBegin: true, + excludeEnd: true, + keywords: KEYWORDS$1, + contains: PARAMS_CONTAINS + }; + + // ES6 classes + const CLASS_OR_EXTENDS = { + variants: [ + { + match: [ + /class/, + /\s+/, + IDENT_RE$1 + ], + scope: { + 1: "keyword", + 3: "title.class" + } + }, + { + match: [ + /extends/, + /\s+/, + concat(IDENT_RE$1, "(", concat(/\./, IDENT_RE$1), ")*") + ], + scope: { + 1: "keyword", + 3: "title.class.inherited" + } + } + ] + }; + + const CLASS_REFERENCE = { + relevance: 0, + match: /\b[A-Z][a-z]+([A-Z][a-z]+)*/, + className: "title.class", + keywords: { + _: [ + // se we still get relevance credit for JS library classes + ...TYPES, + ...ERROR_TYPES + ] + } + }; + + const USE_STRICT = { + label: "use_strict", + className: 'meta', + relevance: 10, + begin: /^\s*['"]use (strict|asm)['"]/ + }; + + const FUNCTION_DEFINITION = { + variants: [ + { + match: [ + /function/, + /\s+/, + IDENT_RE$1, + /(?=\s*\()/ + ] + }, + // anonymous function + { + match: [ + /function/, + /\s*(?=\()/ + ] + } + ], + className: { + 1: "keyword", + 3: "title.function" + }, + label: "func.def", + contains: [PARAMS], + illegal: /%/ + }; + + const UPPER_CASE_CONSTANT = { + relevance: 0, + match: /\b[A-Z][A-Z_]+\b/, + className: "variable.constant" + }; + + function noneOf(list) { + return concat("(?!", list.join("|"), ")"); + } + + const FUNCTION_CALL = { + match: concat( + /\b/, + noneOf([ + ...BUILT_IN_GLOBALS, + "super" + ]), + IDENT_RE$1, lookahead(/\(/)), + className: "title.function", + relevance: 0 + }; + + const PROPERTY_ACCESS = { + begin: concat(/\./, lookahead( + concat(IDENT_RE$1, /(?![0-9A-Za-z$_(])/) + )), + end: IDENT_RE$1, + excludeBegin: true, + keywords: "prototype", + className: "property", + relevance: 0 + }; + + const GETTER_OR_SETTER = { + match: [ + /get|set/, + /\s+/, + IDENT_RE$1, + /(?=\()/ + ], + className: { + 1: "keyword", + 3: "title.function" + }, + contains: [ + { // eat to avoid empty params + begin: /\(\)/ + }, + PARAMS + ] + }; + + const FUNC_LEAD_IN_RE = '(\\(' + + '[^()]*(\\(' + + '[^()]*(\\(' + + '[^()]*' + + '\\)[^()]*)*' + + '\\)[^()]*)*' + + '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>'; + + const FUNCTION_VARIABLE = { + match: [ + /const|var|let/, /\s+/, + IDENT_RE$1, /\s*/, + /=\s*/, + lookahead(FUNC_LEAD_IN_RE) + ], + className: { + 1: "keyword", + 3: "title.function" + }, + contains: [ + PARAMS + ] + }; + + return { + name: 'Javascript', + aliases: ['js', 'jsx', 'mjs', 'cjs'], + keywords: KEYWORDS$1, + // this will be extended by TypeScript + exports: { PARAMS_CONTAINS }, + illegal: /#(?![$_A-z])/, + contains: [ + hljs.SHEBANG({ + label: "shebang", + binary: "node", + relevance: 5 + }), + USE_STRICT, + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + HTML_TEMPLATE, + CSS_TEMPLATE, + TEMPLATE_STRING, + COMMENT, + NUMBER, + CLASS_REFERENCE, + { + className: 'attr', + begin: IDENT_RE$1 + lookahead(':'), + relevance: 0 + }, + FUNCTION_VARIABLE, + { // "value" container + begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*', + keywords: 'return throw case', + relevance: 0, + contains: [ + COMMENT, + hljs.REGEXP_MODE, + { + className: 'function', + // we have to count the parens to make sure we actually have the + // correct bounding ( ) before the =>. There could be any number of + // sub-expressions inside also surrounded by parens. + begin: FUNC_LEAD_IN_RE, + returnBegin: true, + end: '\\s*=>', + contains: [ + { + className: 'params', + variants: [ + { + begin: hljs.UNDERSCORE_IDENT_RE, + relevance: 0 + }, + { + className: null, + begin: /\(\s*\)/, + skip: true + }, + { + begin: /\(/, + end: /\)/, + excludeBegin: true, + excludeEnd: true, + keywords: KEYWORDS$1, + contains: PARAMS_CONTAINS + } + ] + } + ] + }, + { // could be a comma delimited list of params to a function call + begin: /,/, + relevance: 0 + }, + { + match: /\s+/, + relevance: 0 + }, + { // JSX + variants: [ + { begin: FRAGMENT.begin, end: FRAGMENT.end }, + { + begin: XML_TAG.begin, + // we carefully check the opening tag to see if it truly + // is a tag and not a false positive + 'on:begin': XML_TAG.isTrulyOpeningTag, + end: XML_TAG.end + } + ], + subLanguage: 'xml', + contains: [ + { + begin: XML_TAG.begin, + end: XML_TAG.end, + skip: true, + contains: ['self'] + } + ] + } + ], + }, + FUNCTION_DEFINITION, + { + // prevent this from getting swallowed up by function + // since they appear "function like" + beginKeywords: "while if switch catch for" + }, + { + // we have to count the parens to make sure we actually have the correct + // bounding ( ). There could be any number of sub-expressions inside + // also surrounded by parens. + begin: '\\b(?!function)' + hljs.UNDERSCORE_IDENT_RE + + '\\(' + // first parens + '[^()]*(\\(' + + '[^()]*(\\(' + + '[^()]*' + + '\\)[^()]*)*' + + '\\)[^()]*)*' + + '\\)\\s*\\{', // end parens + returnBegin: true, + label: "func.def", + contains: [ + PARAMS, + hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1, className: "title.function" }) + ] + }, + // catch ... so it won't trigger the property rule below + { + match: /\.\.\./, + relevance: 0 + }, + PROPERTY_ACCESS, + // hack: prevents detection of keywords in some circumstances + // .keyword() + // $keyword = x + { + match: '\\$' + IDENT_RE$1, + relevance: 0 + }, + { + match: [/\bconstructor(?=\s*\()/], + className: { 1: "title.function" }, + contains: [PARAMS] + }, + FUNCTION_CALL, + UPPER_CASE_CONSTANT, + CLASS_OR_EXTENDS, + GETTER_OR_SETTER, + { + match: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something` + } + ] + }; + } + + /* + Language: JSON + Description: JSON (JavaScript Object Notation) is a lightweight data-interchange format. + Author: Ivan Sagalaev + Website: http://www.json.org + Category: common, protocols, web + */ + + function json(hljs) { + const ATTRIBUTE = { + className: 'attr', + begin: /"(\\.|[^\\"\r\n])*"(?=\s*:)/, + relevance: 1.01 + }; + const PUNCTUATION = { + match: /[{}[\],:]/, + className: "punctuation", + relevance: 0 + }; + // normally we would rely on `keywords` for this but using a mode here allows us + // to use the very tight `illegal: \S` rule later to flag any other character + // as illegal indicating that despite looking like JSON we do not truly have + // JSON and thus improve false-positively greatly since JSON will try and claim + // all sorts of JSON looking stuff + const LITERALS = { + beginKeywords: [ + "true", + "false", + "null" + ].join(" ") + }; + + return { + name: 'JSON', + contains: [ + ATTRIBUTE, + PUNCTUATION, + hljs.QUOTE_STRING_MODE, + LITERALS, + hljs.C_NUMBER_MODE, + hljs.C_LINE_COMMENT_MODE, + hljs.C_BLOCK_COMMENT_MODE + ], + illegal: '\\S' + }; + } + + + /** @type LanguageFn */ + function xml(hljs) { + // Element names can contain letters, digits, hyphens, underscores, and periods + const TAG_NAME_RE = concat(/[A-Z_]/, optional(/[A-Z0-9_.-]*:/), /[A-Z0-9_.-]*/); + const XML_IDENT_RE = /[A-Za-z0-9._:-]+/; + const XML_ENTITIES = { + className: 'symbol', + begin: /&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/ + }; + const XML_META_KEYWORDS = { + begin: /\s/, + contains: [ + { + className: 'keyword', + begin: /#?[a-z_][a-z1-9_-]+/, + illegal: /\n/ + } + ] + }; + const XML_META_PAR_KEYWORDS = hljs.inherit(XML_META_KEYWORDS, { + begin: /\(/, + end: /\)/ + }); + const APOS_META_STRING_MODE = hljs.inherit(hljs.APOS_STRING_MODE, { + className: 'string' + }); + const QUOTE_META_STRING_MODE = hljs.inherit(hljs.QUOTE_STRING_MODE, { + className: 'string' + }); + const TAG_INTERNALS = { + endsWithParent: true, + illegal: /`]+/ + } + ] + } + ] + } + ] + }; + return { + name: 'HTML, XML', + aliases: [ + 'html', + 'xhtml', + 'rss', + 'atom', + 'xjb', + 'xsd', + 'xsl', + 'plist', + 'wsf', + 'svg' + ], + case_insensitive: true, + contains: [ + { + className: 'meta', + begin: //, + relevance: 10, + contains: [ + XML_META_KEYWORDS, + QUOTE_META_STRING_MODE, + APOS_META_STRING_MODE, + XML_META_PAR_KEYWORDS, + { + begin: /\[/, + end: /\]/, + contains: [ + { + className: 'meta', + begin: //, + contains: [ + XML_META_KEYWORDS, + XML_META_PAR_KEYWORDS, + QUOTE_META_STRING_MODE, + APOS_META_STRING_MODE + ] + } + ] + } + ] + }, + hljs.COMMENT( + //, + { + relevance: 10 + } + ), + { + begin: //, + relevance: 10 + }, + XML_ENTITIES, + { + className: 'meta', + begin: /<\?xml/, + end: /\?>/, + relevance: 10 + }, + { + className: 'tag', + /* + The lookahead pattern (?=...) ensures that 'begin' only matches + ')/, + end: />/, + keywords: { + name: 'style' + }, + contains: [TAG_INTERNALS], + starts: { + end: /<\/style>/, + returnEnd: true, + subLanguage: [ + 'css', + 'xml' + ] + } + }, + { + className: 'tag', + // See the comment in the