Skip to content

Commit

Permalink
Merge branch 'main' into double_rendering_for_get_redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
dhh committed Apr 30, 2022
2 parents ba5a8a3 + e521872 commit 5df3d7d
Show file tree
Hide file tree
Showing 71 changed files with 2,169 additions and 848 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
node_modules/
22 changes: 22 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"prettier"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"prettier/prettier": ["error"],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-empty-function": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
}
}
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ jobs:
- run: yarn install
- run: yarn build

- name: Set Chrome Version
run: |
CHROMEVER="$(chromedriver --version | cut -d' ' -f2)"
echo "Actions ChromeDriver is $CHROMEVER"
CONTENTS="$(jq '.tunnelOptions.drivers[0].name = "chrome"' < intern.json)"
CONTENTS="$(echo ${CONTENTS} | jq --arg chromever "$CHROMEVER" '.tunnelOptions.drivers[0].version = $chromever')"
echo "${CONTENTS}" > intern.json
cat intern.json
- name: Lint
run: yarn lint

- name: Test
run: yarn test

Expand Down
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
node_modules/
5 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"singleQuote": false,
"printWidth": 120,
"semi": false
}
17 changes: 12 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,21 @@
"access": "public"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "9.0.0",
"@rollup/plugin-typescript": "^6.0.0",
"@rollup/plugin-node-resolve": "13.1.3",
"@rollup/plugin-typescript": "8.3.1",
"@types/multer": "^1.4.5",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"arg": "^5.0.1",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"intern": "^4.9.0",
"arg": "^4.1.0",
"multer": "^1.4.2",
"prettier": "2.6.2",
"rollup": "^2.35.1",
"tslib": "^2.0.3",
"typescript": "^4.1.3"
"typescript": "^4.6.3"
},
"scripts": {
"clean": "rm -fr dist",
Expand All @@ -55,6 +61,7 @@
"test": "NODE_OPTIONS=--inspect node src/tests/runner.js",
"test:win": "SET NODE_OPTIONS=--inspect & node src/tests/runner.js",
"prerelease": "yarn build && git --no-pager diff && echo && npm pack --dry-run && echo && read -n 1 -p \"Look OK? Press any key to publish and commit v$npm_package_version\" && echo",
"release": "npm publish && git commit -am \"$npm_package_name v$npm_package_version\" && git push"
"release": "npm publish && git commit -am \"$npm_package_name v$npm_package_version\" && git push",
"lint": "eslint . --ext .ts"
}
}
8 changes: 4 additions & 4 deletions src/core/bardo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ export class Bardo {

enter() {
for (const id in this.permanentElementMap) {
const [, newPermanentElement ] = this.permanentElementMap[id]
const [, newPermanentElement] = this.permanentElementMap[id]
this.replaceNewPermanentElementWithPlaceholder(newPermanentElement)
}
}

leave() {
for (const id in this.permanentElementMap) {
const [ currentPermanentElement ] = this.permanentElementMap[id]
const [currentPermanentElement] = this.permanentElementMap[id]
this.replaceCurrentPermanentElementWithClone(currentPermanentElement)
this.replacePlaceholderWithPermanentElement(currentPermanentElement)
}
Expand All @@ -45,11 +45,11 @@ export class Bardo {
}

getPlaceholderById(id: string) {
return this.placeholders.find(element => element.content == id)
return this.placeholders.find((element) => element.content == id)
}

get placeholders(): HTMLMetaElement[] {
return [ ...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]") ] as any
return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")] as any
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/drive/error_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ export class ErrorRenderer extends Renderer<HTMLBodyElement, PageSnapshot> {
}

get scriptElements() {
return [ ...document.documentElement.querySelectorAll("script") ]
return [...document.documentElement.querySelectorAll("script")]
}
}
66 changes: 41 additions & 25 deletions src/core/drive/form_submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ export interface FormSubmissionDelegate {
formSubmissionFinished(formSubmission: FormSubmission): void
}

export type FormSubmissionResult
= { success: boolean, fetchResponse: FetchResponse }
| { success: false, error: Error }
export type FormSubmissionResult = { success: boolean; fetchResponse: FetchResponse } | { success: false; error: Error }

export enum FormSubmissionState {
initialized,
Expand All @@ -27,15 +25,18 @@ export enum FormSubmissionState {

enum FormEnctype {
urlEncoded = "application/x-www-form-urlencoded",
multipart = "multipart/form-data",
plain = "text/plain"
multipart = "multipart/form-data",
plain = "text/plain",
}

function formEnctypeFromString(encoding: string): FormEnctype {
switch(encoding.toLowerCase()) {
case FormEnctype.multipart: return FormEnctype.multipart
case FormEnctype.plain: return FormEnctype.plain
default: return FormEnctype.urlEncoded
switch (encoding.toLowerCase()) {
case FormEnctype.multipart:
return FormEnctype.multipart
case FormEnctype.plain:
return FormEnctype.plain
default:
return FormEnctype.urlEncoded
}
}

Expand All @@ -50,18 +51,23 @@ export class FormSubmission {
state = FormSubmissionState.initialized
result?: FormSubmissionResult

static confirmMethod(message: string, element: HTMLFormElement):boolean {
static confirmMethod(message: string, _element: HTMLFormElement): boolean {
return confirm(message)
}

constructor(delegate: FormSubmissionDelegate, formElement: HTMLFormElement, submitter?: HTMLElement, mustRedirect = false) {
constructor(
delegate: FormSubmissionDelegate,
formElement: HTMLFormElement,
submitter?: HTMLElement,
mustRedirect = false
) {
this.delegate = delegate
this.formElement = formElement
this.submitter = submitter
this.formData = buildFormData(formElement, submitter)
this.location = expandURL(this.action)
if (this.method == FetchMethod.get) {
mergeFormDataEntries(this.location, [ ...this.body.entries() ])
mergeFormDataEntries(this.location, [...this.body.entries()])
}
this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement)
this.mustRedirect = mustRedirect
Expand All @@ -73,8 +79,10 @@ export class FormSubmission {
}

get action(): string {
const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null
return this.submitter?.getAttribute("formaction") || this.formElement.getAttribute("action") || formElementAction || ""
const formElementAction = typeof this.formElement.action === "string" ? this.formElement.action : null
return (
this.submitter?.getAttribute("formaction") || this.formElement.getAttribute("action") || formElementAction || ""
)
}

get body() {
Expand All @@ -94,13 +102,13 @@ export class FormSubmission {
}

get stringFormData() {
return [ ...this.formData ].reduce((entries, [ name, value ]) => {
return entries.concat(typeof value == "string" ? [[ name, value ]] : [])
return [...this.formData].reduce((entries, [name, value]) => {
return entries.concat(typeof value == "string" ? [[name, value]] : [])
}, [] as [string, string][])
}

get confirmationMessage() {
return this.formElement.getAttribute("data-turbo-confirm")
return this.submitter?.getAttribute("data-turbo-confirm") || this.formElement.getAttribute("data-turbo-confirm")
}

get needsConfirmation() {
Expand All @@ -114,7 +122,9 @@ export class FormSubmission {

if (this.needsConfirmation) {
const answer = FormSubmission.confirmMethod(this.confirmationMessage!, this.formElement)
if (!answer) { return }
if (!answer) {
return
}
}

if (this.state == initialized) {
Expand All @@ -140,14 +150,17 @@ export class FormSubmission {
if (token) {
headers["X-CSRF-Token"] = token
}
headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ")
headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ")
}
}

requestStarted(request: FetchRequest) {
requestStarted(_request: FetchRequest) {
this.state = FormSubmissionState.waiting
this.submitter?.setAttribute("disabled", "")
dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } })
dispatch("turbo:submit-start", {
target: this.formElement,
detail: { formSubmission: this },
})
this.delegate.formSubmissionStarted(this)
}

Expand Down Expand Up @@ -178,10 +191,13 @@ export class FormSubmission {
this.delegate.formSubmissionErrored(this, error)
}

requestFinished(request: FetchRequest) {
requestFinished(_request: FetchRequest) {
this.state = FormSubmissionState.stopped
this.submitter?.removeAttribute("disabled")
dispatch("turbo:submit-end", { target: this.formElement, detail: { formSubmission: this, ...this.result }})
dispatch("turbo:submit-end", {
target: this.formElement,
detail: { formSubmission: this, ...this.result },
})
this.delegate.formSubmissionFinished(this)
}

Expand Down Expand Up @@ -223,9 +239,9 @@ function responseSucceededWithoutRedirect(response: FetchResponse) {
}

function mergeFormDataEntries(url: URL, entries: [string, FormDataEntryValue][]): URL {
const searchParams = new URLSearchParams
const searchParams = new URLSearchParams()

for (const [ name, value ] of entries) {
for (const [name, value] of entries) {
if (value instanceof File) continue

searchParams.append(name, value)
Expand Down
60 changes: 32 additions & 28 deletions src/core/drive/head_snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { Snapshot } from "../snapshot"

type ElementDetailMap = { [outerHTML: string]: ElementDetails }

type ElementDetails = { type?: ElementType, tracked: boolean, elements: Element[] }
type ElementDetails = {
type?: ElementType
tracked: boolean
elements: Element[]
}

type ElementType = "script" | "stylesheet"

Expand All @@ -11,27 +15,27 @@ export class HeadSnapshot extends Snapshot<HTMLHeadElement> {
.filter((element) => !elementIsNoscript(element))
.map((element) => elementWithoutNonce(element))
.reduce((result, element) => {
const { outerHTML } = element
const details: ElementDetails
= outerHTML in result
? result[outerHTML]
: {
type: elementType(element),
tracked: elementIsTracked(element),
elements: []
}
return {
...result,
[outerHTML]: {
...details,
elements: [ ...details.elements, element ]
const { outerHTML } = element
const details: ElementDetails =
outerHTML in result
? result[outerHTML]
: {
type: elementType(element),
tracked: elementIsTracked(element),
elements: [],
}
return {
...result,
[outerHTML]: {
...details,
elements: [...details.elements, element],
},
}
}
}, {} as ElementDetailMap)
}, {} as ElementDetailMap)

get trackedElementSignature(): string {
return Object.keys(this.detailsByOuterHTML)
.filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
.filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)
.join("")
}

Expand All @@ -45,8 +49,8 @@ export class HeadSnapshot extends Snapshot<HTMLHeadElement> {

getElementsMatchingTypeNotInSnapshot(matchedType: ElementType, snapshot: HeadSnapshot) {
return Object.keys(this.detailsByOuterHTML)
.filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
.map(outerHTML => this.detailsByOuterHTML[outerHTML])
.filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))
.map((outerHTML) => this.detailsByOuterHTML[outerHTML])
.filter(({ type }) => type == matchedType)
.map(({ elements: [element] }) => element)
}
Expand All @@ -55,9 +59,9 @@ export class HeadSnapshot extends Snapshot<HTMLHeadElement> {
return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML]
if (type == null && !tracked) {
return [ ...result, ...elements ]
return [...result, ...elements]
} else if (elements.length > 1) {
return [ ...result, ...elements.slice(1) ]
return [...result, ...elements.slice(1)]
} else {
return result
}
Expand All @@ -66,14 +70,14 @@ export class HeadSnapshot extends Snapshot<HTMLHeadElement> {

getMetaValue(name: string): string | null {
const element = this.findMetaElementByName(name)
return element
? element.getAttribute("content")
: null
return element ? element.getAttribute("content") : null
}

findMetaElementByName(name: string) {
return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
const { elements: [element] } = this.detailsByOuterHTML[outerHTML]
const {
elements: [element],
} = this.detailsByOuterHTML[outerHTML]
return elementIsMetaElementWithName(element, name) ? element : result
}, undefined as Element | undefined)
}
Expand Down Expand Up @@ -115,6 +119,6 @@ function elementWithoutNonce(element: Element) {
if (element.hasAttribute("nonce")) {
element.setAttribute("nonce", "")
}
return element

return element
}
Loading

0 comments on commit 5df3d7d

Please sign in to comment.