From 801a4e57fd7dca9d98c85eaedcd818f1a09617c2 Mon Sep 17 00:00:00 2001 From: Brett Morgan Date: Tue, 5 Oct 2021 11:25:29 +1000 Subject: [PATCH 01/36] Add DartPad.dev to the iframe allow list --- claat/nodes/iframe.go | 1 + 1 file changed, 1 insertion(+) diff --git a/claat/nodes/iframe.go b/claat/nodes/iframe.go index 664cb7b30..1d908c489 100644 --- a/claat/nodes/iframe.go +++ b/claat/nodes/iframe.go @@ -6,6 +6,7 @@ var IframeAllowlist = []string{ "carto.com", "codepen.io", "dartlang.org", + "dartpad.dev", "github.com", "glitch.com", "google.com", From 178baa7f61e06e1341a4f5998b6619ea1f8d94a5 Mon Sep 17 00:00:00 2001 From: Janardhan Pulivarthi Date: Sat, 25 Dec 2021 21:14:55 +0530 Subject: [PATCH 02/36] install go module with `go install` instead of `go get` for more: https://go.dev/doc/go-get-install-deprecation --- claat/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claat/README.md b/claat/README.md index 0c82a383b..05eeb1ea3 100644 --- a/claat/README.md +++ b/claat/README.md @@ -14,7 +14,7 @@ The binaries, as well as their checksums are available at the Alternatively, if you have [Go installed](https://golang.org/doc/install): - go get github.com/googlecodelabs/tools/claat + go install github.com/googlecodelabs/tools/claat@latest If none of the above works, compile the tool from source following Dev workflow instructions below. From d323995ff2be867244d2fd3e7fd655f3c3484f5b Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 30 Dec 2021 13:33:11 -0600 Subject: [PATCH 03/36] Snake case metadata table field names (#698) --- claat/parser/gdoc/parse.go | 7 ++++--- claat/parser/gdoc/parse_test.go | 6 +++--- claat/parser/md/parse.go | 7 ++++--- claat/parser/md/parse_test.go | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/claat/parser/gdoc/parse.go b/claat/parser/gdoc/parse.go index 14c8b5d2a..faa564a1c 100644 --- a/claat/parser/gdoc/parse.go +++ b/claat/parser/gdoc/parse.go @@ -32,6 +32,7 @@ import ( "github.com/googlecodelabs/tools/claat/parser" "github.com/googlecodelabs/tools/claat/types" "github.com/googlecodelabs/tools/claat/util" + "github.com/stoewer/go-strcase" ) func init() { @@ -390,7 +391,7 @@ func metaTable(ds *docState) { continue } s := stringifyNode(tr.FirstChild.NextSibling, true, false) - fieldName := strings.ToLower(stringifyNode(tr.FirstChild, true, false)) + fieldName := strcase.SnakeCase(stringifyNode(tr.FirstChild, true, false)) switch fieldName { case "id", "url": ds.clab.ID = s @@ -407,9 +408,9 @@ func metaTable(ds *docState) { v := util.NormalizedSplit(s) sv := types.LegacyStatus(v) ds.clab.Status = &sv - case "feedback", "feedback link": + case "feedback", "feedback_link": ds.clab.Feedback = s - case "analytics", "analytics account", "google analytics": + case "analytics", "analytics_account", "google_analytics": ds.clab.GA = s default: // If not explicitly parsed, it might be a pass_metadata value. diff --git a/claat/parser/gdoc/parse_test.go b/claat/parser/gdoc/parse_test.go index dc0ab7083..6a0ca2363 100644 --- a/claat/parser/gdoc/parse_test.go +++ b/claat/parser/gdoc/parse_test.go @@ -214,7 +214,7 @@ func TestMetaTablePassMetadata(t *testing.T) { Final - Feedback + Feedback Link https://example.com/issues @@ -237,7 +237,7 @@ func TestMetaTablePassMetadata(t *testing.T) { p := &Parser{} opts := *parser.NewOptions() opts.PassMetadata = map[string]bool{ - "extrafieldone": true, + "extra_field_one": true, } clab, err := p.Parse(markupReader(markup), opts) @@ -256,7 +256,7 @@ func TestMetaTablePassMetadata(t *testing.T) { // TODO: move sorting to Parse of the parser package Tags: []string{"kiosk", "web"}, Extra: map[string]string{ - "extrafieldone": "11111", + "extra_field_one": "11111", }, } if !reflect.DeepEqual(clab.Meta, meta) { diff --git a/claat/parser/md/parse.go b/claat/parser/md/parse.go index 84a5adc3b..63090be53 100644 --- a/claat/parser/md/parse.go +++ b/claat/parser/md/parse.go @@ -32,6 +32,7 @@ import ( "golang.org/x/net/html" "golang.org/x/net/html/atom" + "github.com/stoewer/go-strcase" "github.com/googlecodelabs/tools/claat/nodes" "github.com/googlecodelabs/tools/claat/parser" @@ -50,8 +51,8 @@ const ( MetaCategories = "categories" MetaEnvironments = "environments" MetaStatus = "status" - MetaFeedbackLink = "feedback link" - MetaAnalyticsAccount = "analytics account" + MetaFeedbackLink = "feedback_link" + MetaAnalyticsAccount = "analytics_account" MetaTags = "tags" MetaSource = "source" MetaDuration = "duration" @@ -411,7 +412,7 @@ func parseMetadata(ds *docState, opts parser.Options) error { // and assigns the values to any keys that match a codelab metadata field as defined by the meta* constants. func addMetadataToCodelab(m map[string]string, c *types.Codelab, opts parser.Options) error { for k, v := range m { - switch k { + switch strcase.SnakeCase(k) { case MetaAuthors: // Directly assign the summary to the codelab field. c.Authors = v diff --git a/claat/parser/md/parse_test.go b/claat/parser/md/parse_test.go index 0399ccb67..e0ef8dd93 100644 --- a/claat/parser/md/parse_test.go +++ b/claat/parser/md/parse_test.go @@ -180,8 +180,8 @@ authors: john smith summary: abcdefghij categories: not, really environments: kiosk, web -analytics account: 12345 -feedback link: https://www.google.com +analytics_account: 12345 +feedback_link: https://www.google.com --- ` @@ -205,7 +205,7 @@ func TestParseMetadataPassMetadata(t *testing.T) { Feedback: "https://www.google.com", GA: "12345", Extra: map[string]string{ - "extrafieldtwo": "bbbbb", + "extra_field_two": "bbbbb", }, } @@ -215,10 +215,10 @@ authors: john smith summary: abcdefghij categories: not, really environments: kiosk, web -analytics account: 12345 -feedback link: https://www.google.com -extrafieldone: aaaaa -extrafieldtwo: bbbbb +analytics_account: 12345 +feedback_link: https://www.google.com +extra_field_one: aaaaa +extra_field_two: bbbbb --- ` @@ -226,7 +226,7 @@ extrafieldtwo: bbbbb opts := *parser.NewOptions() opts.PassMetadata = map[string]bool{ - "extrafieldtwo": true, + "extra_field_two": true, } c := mustParseCodelab(content, opts) @@ -277,8 +277,8 @@ authors: john smith summary: abcdefghij categories: not, really environments: kiosk, web -analytics account: 12345 -feedback link: https://www.google.com +analytics_account: 12345 +feedback_link: https://www.google.com extrafieldone: aaaaa extrafieldtwo: bbbbb From fff60fddd46d1693986e7030779a87845369621e Mon Sep 17 00:00:00 2001 From: Shawn Busolits Date: Wed, 9 Mar 2022 13:16:22 -0500 Subject: [PATCH 04/36] Fix byte index out of range panic for likely error responses. --- claat/fetch/fetch.go | 6 ++++++ claat/go.mod | 1 + claat/go.sum | 3 +++ 3 files changed, 10 insertions(+) diff --git a/claat/fetch/fetch.go b/claat/fetch/fetch.go index 97c536492..27728c851 100644 --- a/claat/fetch/fetch.go +++ b/claat/fetch/fetch.go @@ -46,6 +46,9 @@ const ( // driveAPI is a base URL for Drive API driveAPI = "https://www.googleapis.com/drive/v3" + + // Minimum image size in bytes for extension detection. + minImageSize = 11 ) // TODO: create an enum for use with "nometa" for readability's sake @@ -270,6 +273,9 @@ func (f *Fetcher) slurpBytes(codelabSrc, dir, imgURL string) (string, error) { } b, err = ioutil.ReadFile(imgURL) ext = filepath.Ext(imgURL) + } else if len(b) < minImageSize { + em := fmt.Sprintf("Error fetching image - response is too small (< %d bytes).", minImageSize) + return "", errors.New(em) } else { b, err = f.slurpRemoteBytes(u.String(), 5) if string(b[6:10]) == "JFIF" { diff --git a/claat/go.mod b/claat/go.mod index 74e2f02bf..6950a1472 100644 --- a/claat/go.mod +++ b/claat/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/google/go-cmp v0.5.6 + github.com/stoewer/go-strcase v1.2.0 // indirect github.com/x1ddos/csslex v0.0.0-20160125172232-7894d8ab8bfe github.com/yuin/goldmark v1.3.7 golang.org/x/net v0.0.0-20210525063256-abc453219eb5 diff --git a/claat/go.sum b/claat/go.sum index 799f289a2..210c6f58d 100644 --- a/claat/go.sum +++ b/claat/go.sum @@ -107,8 +107,11 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/x1ddos/csslex v0.0.0-20160125172232-7894d8ab8bfe h1:SX7lFdwn40ahL78CxofAh548P+dcWjdRNpirU7+sKiE= github.com/x1ddos/csslex v0.0.0-20160125172232-7894d8ab8bfe/go.mod h1:SwmD4V+Y0RjNqvt8hW2FpZNkQnoFVNtBF9qEnevUueU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 79e16b8db7cd674b36e199625da1629e35e69b93 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 22 Mar 2022 11:56:02 -0500 Subject: [PATCH 05/36] Use `eventHandler. listen`. (#718) --- codelab-elements/google-codelab/google_codelab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codelab-elements/google-codelab/google_codelab.js b/codelab-elements/google-codelab/google_codelab.js index 2627e46ab..3f98cf045 100644 --- a/codelab-elements/google-codelab/google_codelab.js +++ b/codelab-elements/google-codelab/google_codelab.js @@ -271,7 +271,7 @@ class Codelab extends HTMLElement { if (this.ready_) { this.firePageLoadEvents_(); } else { - this.addEventListener( + this.eventHandler_.listen(this, CODELAB_READY_EVENT, () => this.firePageLoadEvents_()); } } From 0b0a6c2505acca9b2ececb35e0749085a48abba8 Mon Sep 17 00:00:00 2001 From: Shawn Busolits Date: Tue, 22 Mar 2022 11:18:34 -0400 Subject: [PATCH 06/36] Fix bug with image bytes length detection. --- claat/fetch/fetch.go | 30 +++++++++++++++++++++--------- claat/fetch/fetch_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/claat/fetch/fetch.go b/claat/fetch/fetch.go index 27728c851..6f9d2ca77 100644 --- a/claat/fetch/fetch.go +++ b/claat/fetch/fetch.go @@ -273,17 +273,14 @@ func (f *Fetcher) slurpBytes(codelabSrc, dir, imgURL string) (string, error) { } b, err = ioutil.ReadFile(imgURL) ext = filepath.Ext(imgURL) - } else if len(b) < minImageSize { - em := fmt.Sprintf("Error fetching image - response is too small (< %d bytes).", minImageSize) - return "", errors.New(em) } else { b, err = f.slurpRemoteBytes(u.String(), 5) - if string(b[6:10]) == "JFIF" { - ext = ".jpeg" - } else if string(b[0:3]) == "GIF" { - ext = ".gif" - } else { - ext = ".png" + if err != nil { + var e error + ext, e = imgExtFromBytes(b) + if e != nil { + return "", fmt.Errorf("Error parsing image at %s: %v", u.String(), e) + } } } if err != nil { @@ -515,3 +512,18 @@ func isStdout(filename string) bool { func codelabDir(base string, m *types.Meta) string { return filepath.Join(base, m.ID) } + +func imgExtFromBytes(b []byte) (string, error) { + if len(b) < minImageSize { + em := fmt.Sprintf("Error parsing image - response \"%s\" is too small (< %d bytes).", b, minImageSize) + return "", errors.New(em) + } + ext := ".png" + switch { + case string(b[6:10]) == "JFIF": + ext = ".jpeg" + case string(b[0:3]) == "GIF": + ext = ".gif" + } + return ext, nil +} diff --git a/claat/fetch/fetch_test.go b/claat/fetch/fetch_test.go index dfb5d3326..0f010ceec 100644 --- a/claat/fetch/fetch_test.go +++ b/claat/fetch/fetch_test.go @@ -104,6 +104,34 @@ func TestFuzzRestrictPathToParent(t *testing.T) { } } +func TestImgExtFromBytes(t *testing.T) { + tests := []struct { + bytes []byte + + wantExt string + wantErr bool + }{ + {[]byte("012345JFIF0"), ".jpeg", false}, + {[]byte("GIF34567890"), ".gif", false}, + {[]byte("SOMETHINGELSE"), ".png", false}, + {[]byte("GIF345JFIF0"), ".jpeg", false}, + {[]byte("toosmall"), "", true}, + } + for _, tc := range tests { + t.Run(fmt.Sprintf("bytes: %s", tc.bytes), func(t *testing.T) { + ext, err := imgExtFromBytes(tc.bytes) + + if err != nil != tc.wantErr { + t.Errorf("imgExtFromBytes() error = %v, wantErr %v", err, tc.wantErr) + return + } + if ext != tc.wantExt { + t.Errorf("imgExtFromBytes() return: got %s, wanted %s", ext, tc.wantExt) + } + }) + } +} + // safeAbs compute Abs of p and fail the test if not valid. // Empty string return empty path. func safeAbs(t *testing.T, p string) string { From 21e8ad6159883c595b800c2d3e766d21dc39afd1 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 29 Mar 2022 12:37:16 -0500 Subject: [PATCH 07/36] Add `disconnectedCallback`. (#722) --- .../google-codelab-about/google_codelab_about.js | 6 ++++++ codelab-elements/google-codelab-step/google_codelab_step.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/codelab-elements/google-codelab-about/google_codelab_about.js b/codelab-elements/google-codelab-about/google_codelab_about.js index 0b89a0903..40416ef65 100644 --- a/codelab-elements/google-codelab-about/google_codelab_about.js +++ b/codelab-elements/google-codelab-about/google_codelab_about.js @@ -64,6 +64,12 @@ class CodelabAbout extends HTMLElement { } } + /** + * @export + * @override + */ + disconnectedCallback() {} + /** * @return {!Array} * @export diff --git a/codelab-elements/google-codelab-step/google_codelab_step.js b/codelab-elements/google-codelab-step/google_codelab_step.js index a4d4d02bf..a569d3a22 100644 --- a/codelab-elements/google-codelab-step/google_codelab_step.js +++ b/codelab-elements/google-codelab-step/google_codelab_step.js @@ -96,6 +96,12 @@ class CodelabStep extends HTMLElement { this.setupDom_(); } + /** + * @export + * @override + */ + disconnectedCallback() {} + /** * @return {!Array} * @export From 50f2fc32895ae30f500ede0804d60a3e6baec4ca Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 1 Apr 2022 10:16:43 -0500 Subject: [PATCH 08/36] Simplify page URL hash logic (#725) --- .../google-codelab/google_codelab.js | 55 +++++++------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/codelab-elements/google-codelab/google_codelab.js b/codelab-elements/google-codelab/google_codelab.js index 3f98cf045..76bc23d1b 100644 --- a/codelab-elements/google-codelab/google_codelab.js +++ b/codelab-elements/google-codelab/google_codelab.js @@ -329,6 +329,24 @@ class Codelab extends HTMLElement { this.setAttribute(SELECTED_ATTR, index); } + /** + * @export + * @return{string} + */ + get hash() { + return window.location.hash; + } + + /** + * @export + * @param {string} newHash + */ + set hash(newHash) { + if (newHash !== '' && window.location.hash !== newHash) { + window.location.hash = newHash; + } + } + /** * @protected */ @@ -484,32 +502,6 @@ class Codelab extends HTMLElement { } } - /** - * History popState callback - * @param {!Event} e - * @private - */ - handlePopStateChanged_(e) { - const step = this.getStepFromHash_(document.location.hash); - this.setAttribute(DONT_SET_HISTORY_ATTR, ''); - this.setAttribute(SELECTED_ATTR, `${step}`); - this.removeAttribute(DONT_SET_HISTORY_ATTR); - } - - /** - * Updates the browser history state - * @param {string} path The new browser state - * @param {boolean=} replaceState optionally replace state instead of pushing - * @export - */ - updateHistoryState(path, replaceState = false) { - if (replaceState) { - window.history.replaceState({path}, document.title, path); - } else { - window.history.pushState({path}, document.title, path); - } - } - /** * @param {!Event} e * @private @@ -864,22 +856,15 @@ class Codelab extends HTMLElement { */ init_() { this.id_ = this.getAttribute(ID_ATTR); - let step = this.getStepFromHash_(document.location.hash) || - this.getStepFromStorage_(); + let step = this.getStepFromHash_(this.hash) || this.getStepFromStorage_(); this.setAttribute(SELECTED_ATTR, `${step}`); - this.eventHandler_.listen( - dom.getWindow(), events.EventType.POPSTATE, (e) => { - this.handlePopStateChanged_(e); - }); } /** * @protected */ saveStep() { - if (!this.hasAttribute(DONT_SET_HISTORY_ATTR)) { - this.updateHistoryState(`#${this.currentSelectedStep}`, true); - } + this.hash = `#${this.currentSelectedStep}`; if (this.id_) { this.storage_.set( `progress_${this.id_}`, String(this.currentSelectedStep)); From d977eada2340f660609d0f0bf813c869bfb45687 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 7 Apr 2022 11:51:38 -0500 Subject: [PATCH 09/36] Improve mobile codelab title. --- codelab-elements/google-codelab/google_codelab.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codelab-elements/google-codelab/google_codelab.scss b/codelab-elements/google-codelab/google_codelab.scss index 4d2c0cc73..41a94ba43 100644 --- a/codelab-elements/google-codelab/google_codelab.scss +++ b/codelab-elements/google-codelab/google_codelab.scss @@ -49,6 +49,7 @@ google-codelab #codelab-title { padding: 0 36px 0 16px; -webkit-font-smoothing: antialiased; z-index: 1000; + max-width: 100vw; } google-codelab #codelab-title h1 { @@ -120,6 +121,7 @@ google-codelab #controls { google-codelab #fabs { display: flex; justify-content: space-between; + margin: 0 auto; max-width: 1025px; } @@ -197,6 +199,10 @@ google-codelab .metadata a:focus { padding-top: 0; } + google-codelab #codelab-title { + padding: 0 16px; + } + google-codelab #codelab-title .codelab-time-container { display: none; } From 538f6f75b0f3d832ad3bbe72fce2227eb0fc8572 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 13 Apr 2022 11:22:27 -0500 Subject: [PATCH 10/36] Fix image extension parsing. (#729) --- claat/fetch/fetch.go | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/claat/fetch/fetch.go b/claat/fetch/fetch.go index 6f9d2ca77..93f2b3887 100644 --- a/claat/fetch/fetch.go +++ b/claat/fetch/fetch.go @@ -271,20 +271,17 @@ func (f *Fetcher) slurpBytes(codelabSrc, dir, imgURL string) (string, error) { if imgURL, err = restrictPathToParent(imgURL, filepath.Dir(codelabSrc)); err != nil { return "", err } - b, err = ioutil.ReadFile(imgURL) + if b, err = ioutil.ReadFile(imgURL); err != nil { + return "", err + } ext = filepath.Ext(imgURL) } else { - b, err = f.slurpRemoteBytes(u.String(), 5) - if err != nil { - var e error - ext, e = imgExtFromBytes(b) - if e != nil { - return "", fmt.Errorf("Error parsing image at %s: %v", u.String(), e) - } + if b, err = f.slurpRemoteBytes(u.String(), 5); err != nil { + return "", fmt.Errorf("Error downloading image at %s: %v", u.String(), err) + } + if ext, err = imgExtFromBytes(b); err != nil { + return "", fmt.Errorf("Error reading image type at %s: %v", u.String(), err) } - } - if err != nil { - return "", err } crc := crc64.Checksum(b, f.crcTable) @@ -515,15 +512,14 @@ func codelabDir(base string, m *types.Meta) string { func imgExtFromBytes(b []byte) (string, error) { if len(b) < minImageSize { - em := fmt.Sprintf("Error parsing image - response \"%s\" is too small (< %d bytes).", b, minImageSize) - return "", errors.New(em) - } + return "", fmt.Errorf("error parsing image - response \"%s\" is too small (< %d bytes)", b, minImageSize) + } ext := ".png" switch { - case string(b[6:10]) == "JFIF": - ext = ".jpeg" - case string(b[0:3]) == "GIF": - ext = ".gif" - } + case string(b[6:10]) == "JFIF": + ext = ".jpeg" + case string(b[0:3]) == "GIF": + ext = ".gif" + } return ext, nil } From 7b0573f07e63e13c8fb8b2a1c3259bdfd0dd0fbf Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 14 Apr 2022 14:32:31 -0500 Subject: [PATCH 11/36] Prevent reloads when setting hash. (#731) --- codelab-elements/google-codelab/google_codelab.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codelab-elements/google-codelab/google_codelab.js b/codelab-elements/google-codelab/google_codelab.js index 76bc23d1b..9f7e4ceef 100644 --- a/codelab-elements/google-codelab/google_codelab.js +++ b/codelab-elements/google-codelab/google_codelab.js @@ -271,8 +271,8 @@ class Codelab extends HTMLElement { if (this.ready_) { this.firePageLoadEvents_(); } else { - this.eventHandler_.listen(this, - CODELAB_READY_EVENT, () => this.firePageLoadEvents_()); + this.eventHandler_.listen( + this, CODELAB_READY_EVENT, () => this.firePageLoadEvents_()); } } break; @@ -343,7 +343,7 @@ class Codelab extends HTMLElement { */ set hash(newHash) { if (newHash !== '' && window.location.hash !== newHash) { - window.location.hash = newHash; + window.history.replaceState({newHash}, document.title, newHash); } } From ec84552184208078dd0e0589e4e11224299a9bd0 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 20 Oct 2022 19:05:45 -0500 Subject: [PATCH 12/36] Update export.go --- claat/cmd/export.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/claat/cmd/export.go b/claat/cmd/export.go index 209cc8f56..652452599 100644 --- a/claat/cmd/export.go +++ b/claat/cmd/export.go @@ -111,20 +111,19 @@ func ExportCodelab(src string, rt http.RoundTripper, opts CmdExportOptions) (*ty lastmod := types.ContextTime(clab.Mod) clab.Meta.Source = src meta := &clab.Meta - ctx := &types.Context{ - Env: opts.Expenv, - Format: opts.Tmplout, - Prefix: opts.Prefix, - MainGA: opts.GlobalGA, - Updated: &lastmod, - } dir := opts.Output // output dir or stdout if !isStdout(dir) { dir = codelabDir(dir, meta) } // write codelab and its metadata to disk - return meta, writeCodelab(dir, clab.Codelab, opts.ExtraVars, ctx) + return meta, writeCodelab(dir, clab.Codelab, opts.ExtraVars, &types.Context{ + Env: opts.Expenv, + Format: opts.Tmplout, + Prefix: opts.Prefix, + MainGA: opts.GlobalGA, + Updated: &lastmod, + }) } func ExportCodelabMemory(src io.ReadCloser, w io.Writer, opts CmdExportOptions) (*types.Meta, error) { From f8529efe00765e1bed84f5a7d37d19a9cd039879 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 20 Oct 2022 19:12:33 -0500 Subject: [PATCH 13/36] Update fetch.go --- claat/fetch/fetch.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/claat/fetch/fetch.go b/claat/fetch/fetch.go index 93f2b3887..21dc1ceac 100644 --- a/claat/fetch/fetch.go +++ b/claat/fetch/fetch.go @@ -114,6 +114,7 @@ type Fetcher struct { roundTripper http.RoundTripper } +// NewFetcher creates an instance of Fetcher. func NewFetcher(at string, pm map[string]bool, rt http.RoundTripper) (*Fetcher, error) { return &Fetcher{ authHelper: nil, @@ -479,7 +480,10 @@ func gdocID(url string) string { } func gdocExportURL(id string) string { - return fmt.Sprintf("%s/files/%s/export?mimeType=text/html", driveAPI, id) + q := url.Values{ + "mimeType": {"text/html"}, + } + return fmt.Sprintf("%s/files/%s/export?%s", driveAPI, id, q.Encode()) } // restrictPathToParent will ensure that assetPath is in parent. From 966d39db844d66dd61ccffa7f5d112553a792695 Mon Sep 17 00:00:00 2001 From: Marc Date: Wed, 9 Nov 2022 20:12:24 +0000 Subject: [PATCH 14/36] fix oob oauth to use localhost and update elements bucket (#798) * fix oob oauth to use localhost and update elements bucket * don't display auth code in browser window * fix var name comment --- claat/fetch/drive/auth/auth.go | 30 +++++++++++++++++++++++++----- claat/render/template.html | 10 +++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/claat/fetch/drive/auth/auth.go b/claat/fetch/drive/auth/auth.go index a4ce5ff6f..b0a6df16f 100644 --- a/claat/fetch/drive/auth/auth.go +++ b/claat/fetch/drive/auth/auth.go @@ -18,6 +18,7 @@ import ( "fmt" "io/ioutil" "log" + "net" "net/http" "os" "path" @@ -43,7 +44,7 @@ var ( ClientID: googClient, ClientSecret: googSecret, Scopes: []string{scopeDriveReadOnly}, - RedirectURL: "urn:ietf:wg:oauth:2.0:oob", + RedirectURL: "http://localhost:8091", Endpoint: oauth2.Endpoint{ AuthURL: "https://accounts.google.com/o/oauth2/auth", TokenURL: "https://accounts.google.com/o/oauth2/token", @@ -51,6 +52,25 @@ var ( } ) +// The webserver waits for an oauth code in the three-legged auth flow. +func startWebServer() (code string, err error) { + listener, err := net.Listen("tcp", "localhost:8091") + if err != nil { + return "", err + } + codeCh := make(chan string) + + go http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + code := r.FormValue("code") + codeCh <- code // send code to OAuth flow + listener.Close() + w.Header().Set("Content-Type", "text/plain") + fmt.Fprintf(w, "Received oauth code\r\nYou can now safely close this browser window.") + })) + code = <- codeCh + return code, nil +} + type authorizationHandler func(conf *oauth2.Config) (*oauth2.Token, error) type internalOptions struct { @@ -214,10 +234,10 @@ func (c *cachedTokenSource) Token() (*oauth2.Token, error) { // authorize performs user authorization flow, asking for permissions grant. func authorize(conf *oauth2.Config) (*oauth2.Token, error) { - aurl := conf.AuthCodeURL("unused", oauth2.AccessTypeOffline) - fmt.Printf("Authorize me at following URL, please:\n\n%s\n\nCode: ", aurl) - var code string - if _, err := fmt.Scan(&code); err != nil { + aURL := conf.AuthCodeURL("unused", oauth2.AccessTypeOffline) + fmt.Printf("Authorize me at following URL, please:\n\n%s\n", aURL) + code, err := startWebServer() + if err != nil { return nil, err } return conf.Exchange(context.Background(), code) diff --git a/claat/render/template.html b/claat/render/template.html index 8ef0f442b..dd56bbf23 100644 --- a/claat/render/template.html +++ b/claat/render/template.html @@ -23,7 +23,7 @@ {{.Meta.Title}} - + - + Date: Thu, 22 Jun 2023 14:47:16 -0400 Subject: [PATCH 32/36] Update codelab.go --- claat/types/codelab.go | 1 + 1 file changed, 1 insertion(+) diff --git a/claat/types/codelab.go b/claat/types/codelab.go index cfe7a85b3..fdb0ddd82 100644 --- a/claat/types/codelab.go +++ b/claat/types/codelab.go @@ -35,6 +35,7 @@ type Meta struct { Tags []string `json:"tags"` // All environments supported by the codelab Feedback string `json:"feedback,omitempty"` // Issues and bugs are sent here GA string `json:"ga,omitempty"` // Codelab-specific GA tracking ID + GA4 string `json:"ga4,omitempty"` // Codelab-specific GA4 tracking ID Extra map[string]string `json:"extra,omitempty"` // Extra metadata specified in pass_metadata URL string `json:"url"` // Legacy ID; TODO: remove From b2931352b95c6a4d8d70c47b6c0bdc57a2d7c0c9 Mon Sep 17 00:00:00 2001 From: JasonCent <137332364+JasonCent@users.noreply.github.com> Date: Thu, 22 Jun 2023 16:10:39 -0400 Subject: [PATCH 33/36] Update google_codelab_analytics.js --- .../google_codelab_analytics.js | 160 +----------------- 1 file changed, 3 insertions(+), 157 deletions(-) diff --git a/codelab-elements/google-codelab-analytics/google_codelab_analytics.js b/codelab-elements/google-codelab-analytics/google_codelab_analytics.js index 5039acc4d..3c1df8970 100644 --- a/codelab-elements/google-codelab-analytics/google_codelab_analytics.js +++ b/codelab-elements/google-codelab-analytics/google_codelab_analytics.js @@ -17,10 +17,7 @@ goog.module('googlecodelabs.CodelabAnalytics'); -const Const = goog.require('goog.string.Const'); const EventHandler = goog.require('goog.events.EventHandler'); -const TrustedResourceUrl = goog.require('goog.html.TrustedResourceUrl'); -const {safeScriptEl} = goog.require('safevalues.dom'); /** * The general codelab action event fired for trackable interactions. @@ -41,24 +38,6 @@ const PAGEVIEW_EVENT = 'google-codelab-pageview'; */ const GAID_ATTR = 'gaid'; -/** - * The Google Analytics GA4 ID. - * @const {string} - */ -const GA4ID_ATTR = 'ga4id'; - -/** @const {string} */ -const GTAG = 'gtag'; - -/** - * Namespaced data layer for use with GA4 properties. Allows for independent - * data layers so that other data layers, like that for GTM, don't receive data - * they don't need. - * - * @const {string} - */ -const CODELAB_DATA_LAYER = 'codelabDataLayer'; - /** @const {string} */ const CODELAB_ID_ATTR = 'codelab-id'; @@ -68,12 +47,6 @@ const CODELAB_ID_ATTR = 'codelab-id'; */ const CODELAB_GAID_ATTR = 'codelab-gaid'; -/** - * The GA4ID defined by the current codelab. - * @const {string} - */ -const CODELAB_GA4ID_ATTR = 'codelab-ga4id'; - /** @const {string} */ const CODELAB_ENV_ATTR = 'environment'; @@ -130,9 +103,6 @@ class CodelabAnalytics extends HTMLElement { /** @private {?string} */ this.gaid_; - /** @private {?string} */ - this.ga4Id_; - /** @private {?string} */ this.codelabId_; @@ -155,9 +125,8 @@ class CodelabAnalytics extends HTMLElement { */ connectedCallback() { this.gaid_ = this.getAttribute(GAID_ATTR) || ''; - this.ga4Id_ = this.getAttribute(GA4ID_ATTR) || ''; - if (this.hasSetup_ || (!this.gaid_ && !this.ga4Id_)) { + if (this.hasSetup_ || !this.gaid_) { return; } @@ -170,14 +139,6 @@ class CodelabAnalytics extends HTMLElement { } else { this.init_(); } - - if (this.ga4Id_) { - this.initializeGa4_(); - } - - if (this.ga4Id_ && !this.gaid_) { - this.addEventListeners_(); - } } /** @private */ @@ -192,7 +153,7 @@ class CodelabAnalytics extends HTMLElement { addEventListeners_() { this.eventHandler_.listen(document.body, ACTION_EVENT, (e) => { - const detail = /** @type {!AnalyticsTrackingEvent} */ ( + const detail = /** @type {AnalyticsTrackingEvent} */ ( e.getBrowserEvent().detail); // Add tracking... this.trackEvent_( @@ -201,7 +162,7 @@ class CodelabAnalytics extends HTMLElement { this.eventHandler_.listen(document.body, PAGEVIEW_EVENT, (e) => { - const detail = /** @type {!AnalyticsPageview} */ ( + const detail = /** @type {AnalyticsPageview} */ ( e.getBrowserEvent().detail); this.trackPageview_(detail['page'], detail['title']); }); @@ -255,7 +216,6 @@ class CodelabAnalytics extends HTMLElement { * @private */ trackEvent_(category, opt_action, opt_label) { - // UA related section. const params = { // Always event for trackEvent_ method 'hitType': 'event', @@ -267,30 +227,6 @@ class CodelabAnalytics extends HTMLElement { 'eventLabel': opt_label || '', }; this.gaSend_(params); - - // GA4 related section. - if (!this.getGa4Ids_().length) { - return; - } - - window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; - window[GTAG] = window[GTAG] || function() { - window[CODELAB_DATA_LAYER].push(arguments); - }; - - for (const ga4Id of this.getGa4Ids_()) { - window[GTAG]('event', category, { - // Snakecase naming convention is followed for all built-in GA4 event - // properties. - 'send_to': ga4Id, - // Camelcase naming convention is followed for all custom dimensions - // constructed in the custom element. - 'eventAction': opt_action || '', - 'eventLabel': opt_label || '', - 'codelabEnv': this.codelabEnv_ || '', - 'codelabId': this.codelabId_ || '', - }); - } } /** @@ -299,7 +235,6 @@ class CodelabAnalytics extends HTMLElement { * @private */ trackPageview_(opt_page, opt_title) { - // UA related section. const params = { 'hitType': 'pageview', 'dimension1': this.codelabEnv_, @@ -309,33 +244,6 @@ class CodelabAnalytics extends HTMLElement { 'title': opt_title || '' }; this.gaSend_(params); - - // GA4 related section. - if (!this.getGa4Ids_().length) { - return; - } - - window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; - window[GTAG] = window[GTAG] || function() { - window[CODELAB_DATA_LAYER].push(arguments); - }; - - for (const ga4Id of this.getGa4Ids_()) { - window[GTAG]('event', 'page_view', { - // Snakecase naming convention is followed for all built-in GA4 event - // properties. - 'send_to': ga4Id, - 'page_location': - `${document.location.origin}${document.location.pathname}`, - 'page_path': opt_page || '', - 'page_title': opt_title || '', - // Camelcase naming convention is followed for all custom dimensions - // constructed in the custom element. - 'codelabCategory': this.codelabCategory_ || '', - 'codelabEnv': this.codelabEnv_ || '', - 'codelabId': this.codelabId_ || '', - }); - } } /** @@ -477,68 +385,6 @@ class CodelabAnalytics extends HTMLElement { } return isCreated; } - - /** - * Gets all GA4 IDs for the current page. - * @return {!Array} - * @private - */ - getGa4Ids_() { - if (!this.ga4Id_) { - return []; - } - const ga4Ids = []; - ga4Ids.push(this.ga4Id_); - const codelabGa4Id = this.getAttribute(CODELAB_GA4ID_ATTR); - if (codelabGa4Id) { - ga4Ids.push(codelabGa4Id); - } - if (ga4Ids.length) { - return ga4Ids; - } - return []; - } - - /** - * Initialize the gtag script element and namespaced data layer based on the - * codelabs primary GA4 ID. - * @private - */ - initializeGa4_() { - if (!this.ga4Id_) { - return; - } - - // First, set the GTAG data layer before pushing anything to it. - window[CODELAB_DATA_LAYER] = window[CODELAB_DATA_LAYER] || []; - - const firstScriptElement = document.getElementsByTagName('script')[0]; - const gtagScriptElement = /** @type {!HTMLScriptElement} */ ( - document.createElement('script')); - gtagScriptElement.async = true; - // Key for the formatted params below: - // 'id': the stream id for the GA4 analytics property. The gtag script - // element must only be created once, and only the ID of the primary - // stream is appended when creating the src for that element. - // Additional streams are initialized via the function call - // `window[GTAG]('config', ga4Id...` - // 'l': the namespaced dataLayer used to separate codelabs related GA4 - // data from other data layers that may exist on a site or page. - safeScriptEl.setSrc( - gtagScriptElement, TrustedResourceUrl.formatWithParams( - Const.from('//www.googletagmanager.com/gtag/js'), - {}, {'id': this.ga4Id_, 'l': CODELAB_DATA_LAYER})); - firstScriptElement.parentNode.insertBefore( - gtagScriptElement, firstScriptElement); - - window[GTAG] = function() { - window[CODELAB_DATA_LAYER].push(arguments); - }; - window[GTAG]('js', new Date(Date.now())); - - // Set send_page_view to false. We send pageviews manually. - window[GTAG]('config', this.ga4Id_, {send_page_view: false}); - } } exports = CodelabAnalytics; From 27f42e2806ed8845d518043799e1c0945c322e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Greffier?= Date: Wed, 30 Aug 2023 00:56:10 +0200 Subject: [PATCH 34/36] feat: add stackblitz.com in iframe whitelist (#787) --- claat/nodes/iframe.go | 1 + 1 file changed, 1 insertion(+) diff --git a/claat/nodes/iframe.go b/claat/nodes/iframe.go index 13e12bd43..9a5d1aa32 100644 --- a/claat/nodes/iframe.go +++ b/claat/nodes/iframe.go @@ -14,6 +14,7 @@ var IframeAllowlist = []string{ "google.dev", "observablehq.com", "repl.it", + "stackblitz.com", "web.dev", } From dfb356ff05ccbfb731db66920500ad502cb34fc9 Mon Sep 17 00:00:00 2001 From: Marc Date: Wed, 27 Sep 2023 18:24:40 -0400 Subject: [PATCH 35/36] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index eda1ffa43..1480428d0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Tools for authoring and serving codelabs -[![Demo](https://storage.googleapis.com/claat/demo.png)](https://storage.googleapis.com/claat/demo.mp4) - Codelabs are interactive instructional tutorials, which can be authored in Google Docs using some simple formatting conventions. You can also author codelabs using markdown syntax. This repo contains all the tools and documentation you’ll need for building and publishing From 873fe39d02dcbd43005a5c44f6310595d6d9aa3e Mon Sep 17 00:00:00 2001 From: Ross Beale Date: Tue, 20 Feb 2024 11:53:35 +0000 Subject: [PATCH 36/36] add vimeo (#825) --- claat/nodes/iframe.go | 1 + 1 file changed, 1 insertion(+) diff --git a/claat/nodes/iframe.go b/claat/nodes/iframe.go index 9a5d1aa32..05fc593f0 100644 --- a/claat/nodes/iframe.go +++ b/claat/nodes/iframe.go @@ -15,6 +15,7 @@ var IframeAllowlist = []string{ "observablehq.com", "repl.it", "stackblitz.com", + "vimeo.com", "web.dev", }