fix: ensure cross-platform compatibility with file exports (#4336)
This commit is contained in:
@@ -48,7 +48,7 @@ import { getTeamCollectionJSON } from "~/helpers/backend/helpers"
|
|||||||
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
||||||
import { gistExporter } from "~/helpers/import-export/export/gist"
|
import { gistExporter } from "~/helpers/import-export/export/gist"
|
||||||
import { myCollectionsExporter } from "~/helpers/import-export/export/myCollections"
|
import { myCollectionsExporter } from "~/helpers/import-export/export/myCollections"
|
||||||
import { teamCollectionsExporter } from "~/helpers/import-export/export/teamCollections"
|
import { teamCollectionsExporter } from "~/helpers/import-export/export/teamCollections"
|
||||||
@@ -389,27 +389,27 @@ const HoppMyCollectionsExporter: ImporterOrExporter = {
|
|||||||
applicableTo: ["personal-workspace"],
|
applicableTo: ["personal-workspace"],
|
||||||
isLoading: isHoppMyCollectionExporterInProgress,
|
isLoading: isHoppMyCollectionExporterInProgress,
|
||||||
},
|
},
|
||||||
action: () => {
|
action: async () => {
|
||||||
if (!myCollections.value.length) {
|
if (!myCollections.value.length) {
|
||||||
return toast.error(t("error.no_collections_to_export"))
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
isHoppMyCollectionExporterInProgress.value = true
|
isHoppMyCollectionExporterInProgress.value = true
|
||||||
|
|
||||||
const message = initializeDownloadCollection(
|
const message = await initializeDownloadFile(
|
||||||
myCollectionsExporter(myCollections.value),
|
myCollectionsExporter(myCollections.value),
|
||||||
"Collections"
|
"hoppscotch-personal-collections"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (E.isRight(message)) {
|
E.isRight(message)
|
||||||
toast.success(t(message.right))
|
? toast.success(t(message.right))
|
||||||
|
: toast.error(t(message.left))
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
type: "HOPP_EXPORT_COLLECTION",
|
||||||
exporter: "json",
|
exporter: "json",
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
isHoppMyCollectionExporterInProgress.value = false
|
isHoppMyCollectionExporterInProgress.value = false
|
||||||
},
|
},
|
||||||
@@ -436,7 +436,14 @@ const HoppTeamCollectionsExporter: ImporterOrExporter = {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (E.isRight(res)) {
|
if (E.isRight(res)) {
|
||||||
initializeDownloadCollection(res.right, "team-collections")
|
const message = await initializeDownloadFile(
|
||||||
|
res.right,
|
||||||
|
"hoppscotch-team-collections"
|
||||||
|
)
|
||||||
|
|
||||||
|
E.isRight(message)
|
||||||
|
? toast.success(t(message.right))
|
||||||
|
: toast.error(t(message.left))
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
type: "HOPP_EXPORT_COLLECTION",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { GistSource } from "~/helpers/import-export/import/import-sources/GistSo
|
|||||||
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
@@ -133,14 +133,14 @@ const GqlCollectionsHoppExporter: ImporterOrExporter = {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
applicableTo: ["personal-workspace", "team-workspace"],
|
applicableTo: ["personal-workspace", "team-workspace"],
|
||||||
},
|
},
|
||||||
action: () => {
|
action: async () => {
|
||||||
if (!gqlCollections.value.length) {
|
if (!gqlCollections.value.length) {
|
||||||
return toast.error(t("error.no_collections_to_export"))
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = initializeDownloadCollection(
|
const message = await initializeDownloadFile(
|
||||||
gqlCollectionsExporter(gqlCollections.value),
|
gqlCollectionsExporter(gqlCollections.value),
|
||||||
"GQLCollections"
|
"hoppscotch-gql-collections"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (E.isLeft(message)) {
|
if (E.isLeft(message)) {
|
||||||
|
|||||||
@@ -13,36 +13,36 @@ import { Environment, NonSecretEnvironment } from "@hoppscotch/data"
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
|
||||||
|
import { ImporterOrExporter } from "~/components/importExport/types"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { ImporterOrExporter } from "~/components/importExport/types"
|
import { hoppEnvImporter } from "~/helpers/import-export/import/hoppEnv"
|
||||||
import { FileSource } from "~/helpers/import-export/import/import-sources/FileSource"
|
import { FileSource } from "~/helpers/import-export/import/import-sources/FileSource"
|
||||||
import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource"
|
import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource"
|
||||||
import { hoppEnvImporter } from "~/helpers/import-export/import/hoppEnv"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
appendEnvironments,
|
|
||||||
addGlobalEnvVariable,
|
addGlobalEnvVariable,
|
||||||
|
appendEnvironments,
|
||||||
environments$,
|
environments$,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
|
|
||||||
import { createTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { CreateTeamEnvironmentMutation } from "~/helpers/backend/graphql"
|
import { CreateTeamEnvironmentMutation } from "~/helpers/backend/graphql"
|
||||||
import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv"
|
import { createTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||||
import { insomniaEnvImporter } from "~/helpers/import-export/import/insomniaEnv"
|
import { insomniaEnvImporter } from "~/helpers/import-export/import/insomniaEnv"
|
||||||
|
import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv"
|
||||||
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
|
||||||
import IconPostman from "~icons/hopp/postman"
|
|
||||||
import IconInsomnia from "~icons/hopp/insomnia"
|
|
||||||
import IconUser from "~icons/lucide/user"
|
|
||||||
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
||||||
import { environmentsExporter } from "~/helpers/import-export/export/environments"
|
import { environmentsExporter } from "~/helpers/import-export/export/environments"
|
||||||
import { gistExporter } from "~/helpers/import-export/export/gist"
|
import { gistExporter } from "~/helpers/import-export/export/gist"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import IconInsomnia from "~icons/hopp/insomnia"
|
||||||
|
import IconPostman from "~icons/hopp/postman"
|
||||||
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
|
import IconUser from "~icons/lucide/user"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -67,19 +67,17 @@ const isTeamEnvironment = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const environmentJson = computed(() => {
|
const environmentJson = computed(() => {
|
||||||
if (
|
if (isTeamEnvironment.value && props.teamEnvironments) {
|
||||||
props.environmentType === "TEAM_ENV" &&
|
return props.teamEnvironments.map((x) => x.environment)
|
||||||
props.teamEnvironments !== undefined
|
|
||||||
) {
|
|
||||||
const teamEnvironments = props.teamEnvironments.map(
|
|
||||||
(x) => x.environment as Environment
|
|
||||||
)
|
|
||||||
return teamEnvironments
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return myEnvironments.value
|
return myEnvironments.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const workspaceType = computed(() =>
|
||||||
|
isTeamEnvironment.value ? "team" : "personal"
|
||||||
|
)
|
||||||
|
|
||||||
const HoppEnvironmentsImport: ImporterOrExporter = {
|
const HoppEnvironmentsImport: ImporterOrExporter = {
|
||||||
metadata: {
|
metadata: {
|
||||||
id: "import.from_json",
|
id: "import.from_json",
|
||||||
@@ -105,7 +103,7 @@ const HoppEnvironmentsImport: ImporterOrExporter = {
|
|||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_IMPORT_ENVIRONMENT",
|
type: "HOPP_IMPORT_ENVIRONMENT",
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
workspaceType: isTeamEnvironment.value ? "team" : "personal",
|
workspaceType: workspaceType.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
@@ -138,7 +136,7 @@ const PostmanEnvironmentsImport: ImporterOrExporter = {
|
|||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_IMPORT_ENVIRONMENT",
|
type: "HOPP_IMPORT_ENVIRONMENT",
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
workspaceType: isTeamEnvironment.value ? "team" : "personal",
|
workspaceType: workspaceType.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
@@ -178,7 +176,7 @@ const insomniaEnvironmentsImport: ImporterOrExporter = {
|
|||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_IMPORT_ENVIRONMENT",
|
type: "HOPP_IMPORT_ENVIRONMENT",
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
workspaceType: isTeamEnvironment.value ? "team" : "personal",
|
workspaceType: workspaceType.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
@@ -214,7 +212,7 @@ const EnvironmentsImportFromGIST: ImporterOrExporter = {
|
|||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_IMPORT_ENVIRONMENT",
|
type: "HOPP_IMPORT_ENVIRONMENT",
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
workspaceType: isTeamEnvironment.value ? "team" : "personal",
|
workspaceType: workspaceType.value,
|
||||||
})
|
})
|
||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
},
|
},
|
||||||
@@ -230,14 +228,14 @@ const HoppEnvironmentsExport: ImporterOrExporter = {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
applicableTo: ["personal-workspace", "team-workspace"],
|
applicableTo: ["personal-workspace", "team-workspace"],
|
||||||
},
|
},
|
||||||
action: () => {
|
action: async () => {
|
||||||
if (!environmentJson.value.length) {
|
if (!environmentJson.value.length) {
|
||||||
return toast.error(t("error.no_environments_to_export"))
|
return toast.error(t("error.no_environments_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = initializeDownloadCollection(
|
const message = await initializeDownloadFile(
|
||||||
environmentsExporter(environmentJson.value),
|
environmentsExporter(environmentJson.value),
|
||||||
"Environments"
|
`hoppscotch-${workspaceType.value}-environments`
|
||||||
)
|
)
|
||||||
|
|
||||||
if (E.isLeft(message)) {
|
if (E.isLeft(message)) {
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ import { useService } from "dioc/vue"
|
|||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { computed, ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
import { exportAsJSON } from "~/helpers/import-export/export/environment"
|
||||||
import {
|
import {
|
||||||
createEnvironment,
|
createEnvironment,
|
||||||
@@ -163,11 +165,13 @@ const secretEnvironmentService = useService(SecretEnvironmentService)
|
|||||||
|
|
||||||
const isGlobalEnvironment = computed(() => props.environmentIndex === "Global")
|
const isGlobalEnvironment = computed(() => props.environmentIndex === "Global")
|
||||||
|
|
||||||
const exportEnvironmentAsJSON = () => {
|
const exportEnvironmentAsJSON = async () => {
|
||||||
const { environment, environmentIndex } = props
|
const { environment, environmentIndex } = props
|
||||||
exportAsJSON(environment, environmentIndex)
|
|
||||||
? toast.success(t("state.download_started"))
|
const message = await exportAsJSON(environment, environmentIndex)
|
||||||
: toast.error(t("state.download_failed"))
|
E.isRight(message)
|
||||||
|
? toast.success(t(message.right))
|
||||||
|
: toast.error(t(message.left))
|
||||||
}
|
}
|
||||||
|
|
||||||
const tippyActions = ref<HTMLDivElement | null>(null)
|
const tippyActions = ref<HTMLDivElement | null>(null)
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ import * as TE from "fp-ts/TaskEither"
|
|||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
@@ -161,10 +162,12 @@ const secretEnvironmentService = useService(SecretEnvironmentService)
|
|||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
const exportEnvironmentAsJSON = () =>
|
const exportEnvironmentAsJSON = async () => {
|
||||||
exportAsJSON(props.environment)
|
const message = await exportAsJSON(props.environment)
|
||||||
? toast.success(t("state.download_started"))
|
E.isRight(message)
|
||||||
: toast.error(t("state.download_failed"))
|
? toast.success(t(message.right))
|
||||||
|
: toast.error(t(message.left))
|
||||||
|
}
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
const options = ref<TippyComponent | null>(null)
|
const options = ref<TippyComponent | null>(null)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment } from "@hoppscotch/data"
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
import * as E from "fp-ts/Either"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const getEnvironmentJson = (
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
|
import { initializeDownloadFile } from "."
|
||||||
|
|
||||||
|
const getEnvironmentJSON = (
|
||||||
environmentObj: TeamEnvironment | Environment,
|
environmentObj: TeamEnvironment | Environment,
|
||||||
environmentIndex?: number | "Global" | null
|
environmentIndex?: number | "Global" | null
|
||||||
) => {
|
) => {
|
||||||
@@ -22,33 +24,20 @@ const getEnvironmentJson = (
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const exportAsJSON = (
|
export const exportAsJSON = async (
|
||||||
environmentObj: Environment | TeamEnvironment,
|
environmentObj: Environment | TeamEnvironment,
|
||||||
environmentIndex?: number | "Global" | null
|
environmentIndex?: number | "Global" | null
|
||||||
): boolean => {
|
): Promise<E.Right<string> | E.Left<string>> => {
|
||||||
const dataToWrite = getEnvironmentJson(environmentObj, environmentIndex)
|
const environmentJSON = getEnvironmentJSON(environmentObj, environmentIndex)
|
||||||
|
|
||||||
if (!dataToWrite) return false
|
if (!environmentJSON) {
|
||||||
|
return E.left("state.download_failed")
|
||||||
|
}
|
||||||
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const message = await initializeDownloadFile(
|
||||||
const url = URL.createObjectURL(file)
|
environmentJSON,
|
||||||
|
JSON.parse(environmentJSON).name
|
||||||
|
)
|
||||||
|
|
||||||
URL.revokeObjectURL(url)
|
return message
|
||||||
|
|
||||||
platform.io.saveFileWithDialog({
|
|
||||||
data: dataToWrite,
|
|
||||||
contentType: "application/json",
|
|
||||||
// Extracts the path from url, removes fragment identifier and query parameters if any, appends the ".json" extension, and assigns it
|
|
||||||
suggestedFilename: `${
|
|
||||||
url.split("/").pop()!.split("#")[0].split("?")[0]
|
|
||||||
}.json`,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "JSON file",
|
|
||||||
extensions: ["json"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a downloadable file from a collection and prompts the user to download it.
|
* Create a downloadable file from a collection/environment and prompts the user to download it.
|
||||||
* @param collectionJSON - JSON string of the collection
|
* @param contentsJSON - JSON string of the collection
|
||||||
* @param name - Name of the collection set as the file name
|
* @param name - Name of the collection set as the file name
|
||||||
|
* @returns {Promise<E.Right<string> | E.Left<string>>} - Returns a promise that resolves to an `Either` with `i18n` key for the status message
|
||||||
*/
|
*/
|
||||||
export const initializeDownloadCollection = (
|
export const initializeDownloadFile = async (
|
||||||
collectionJSON: string,
|
contentsJSON: string,
|
||||||
name: string | null
|
name: string | null
|
||||||
) => {
|
) => {
|
||||||
const file = new Blob([collectionJSON], { type: "application/json" })
|
const file = new Blob([contentsJSON], { type: "application/json" })
|
||||||
const a = document.createElement("a")
|
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
a.href = url
|
|
||||||
|
|
||||||
if (name) {
|
const fileName = name ?? url.split("/").pop()!.split("#")[0].split("?")[0]
|
||||||
a.download = `${name}.json`
|
|
||||||
} else {
|
const result = await platform.io.saveFileWithDialog({
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
data: contentsJSON,
|
||||||
|
contentType: "application/json",
|
||||||
|
suggestedFilename: `${fileName}.json`,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Hoppscotch Collection/Environment JSON file",
|
||||||
|
extensions: ["json"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.type === "unknown" || result.type === "saved") {
|
||||||
|
return E.right("state.download_started")
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.appendChild(a)
|
return E.left("state.download_failed")
|
||||||
a.click()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(a)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
return E.right("state.download_started")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,17 @@ export const browserIODef: IOPlatformDef = {
|
|||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
|
||||||
a.href = url
|
a.href = url
|
||||||
a.download = pipe(
|
a.download =
|
||||||
url,
|
opts.suggestedFilename ??
|
||||||
S.split("/"),
|
pipe(
|
||||||
RNEA.last,
|
url,
|
||||||
S.split("#"),
|
S.split("/"),
|
||||||
RNEA.head,
|
RNEA.last,
|
||||||
S.split("?"),
|
S.split("#"),
|
||||||
RNEA.head
|
RNEA.head,
|
||||||
)
|
S.split("?"),
|
||||||
|
RNEA.head
|
||||||
|
)
|
||||||
|
|
||||||
document.body.appendChild(a)
|
document.body.appendChild(a)
|
||||||
a.click()
|
a.click()
|
||||||
|
|||||||
Reference in New Issue
Block a user