├── .gitignore ├── CITATION.cff ├── README.md ├── exercises ├── A2_The_Youtube_API_exercises_question.html ├── A3_tuber_exercises_question.html ├── A4_Preprocessing_and_cleaning_data_exercises_question.html ├── B1_Basic_text_analysis_exercises_question.html └── B2_Sentiment_analysis_of_user_comments_exercises_question.html ├── scripts ├── Authentication.R ├── CamelCase.R ├── ExtractEmoji.R ├── ReplaceEmoji.R ├── YouTubeAPIAccess.R ├── YouTubeSubtitles.R ├── emoji_mapping_function.R ├── install_packages.R ├── video_details_to_df.R └── yt_parse.R ├── slides ├── A0_YouTube_API_Setup │ ├── A0_YouTubeAPISetup.Rmd │ ├── A0_YouTubeAPISetup.html │ ├── A0_YouTubeAPISetup.pdf │ ├── Images │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 16.png │ │ ├── 17.png │ │ ├── 18.png │ │ ├── 19.png │ │ ├── 2.png │ │ ├── 20.png │ │ ├── 21.png │ │ ├── 22.png │ │ ├── 23.png │ │ ├── 24.png │ │ ├── 25.png │ │ ├── 26.png │ │ ├── 27.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ └── libs │ │ ├── clipboard │ │ └── clipboard.min.js │ │ ├── header-attrs │ │ └── header-attrs.js │ │ ├── remark-css │ │ ├── default-fonts.css │ │ └── default.css │ │ ├── tile-view │ │ ├── tile-view.css │ │ └── tile-view.js │ │ ├── xaringanExtra-clipboard │ │ ├── xaringanExtra-clipboard.css │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ └── xaringanExtra-extra-styles.css ├── A1_Introduction │ ├── A1_Intro.Rmd │ ├── A1_Intro.html │ ├── A1_Intro.pdf │ ├── images │ │ └── camel_snake_kebab.jpg │ └── libs │ │ ├── clipboard │ │ └── clipboard.min.js │ │ ├── header-attrs │ │ └── header-attrs.js │ │ ├── remark-css │ │ ├── default-fonts.css │ │ └── default.css │ │ ├── tile-view │ │ ├── tile-view.css │ │ └── tile-view.js │ │ ├── twitter-widget │ │ └── widgets.js │ │ ├── xaringanExtra-clipboard │ │ ├── xaringanExtra-clipboard.css │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ └── xaringanExtra-extra-styles.css ├── A2_The_YouTube_API │ ├── A2_The_YouTube_API.Rmd │ ├── A2_The_YouTube_API.html │ ├── Automatic Sampling and Analysis of YouTube Data.pdf │ ├── Images │ │ ├── API-call-construction.png │ │ ├── API_harvesting.png │ │ ├── BBC2.png │ │ ├── BBC_description.png │ │ ├── BBC_descriptionHTML.png │ │ ├── Metrics.png │ │ ├── Odyssey4.jpg │ │ ├── QuotaLimit_cut.png │ │ ├── Quotas.png │ │ ├── RSeleniumScreenshot.png │ │ ├── RSeleniumScreenshot2.png │ │ ├── RSeleniumScreenshot3.png │ │ ├── RateLimitReduction.png │ │ ├── RateLimitReduction2.png │ │ ├── RateLimitReduction3.png │ │ ├── Screenscraping.png │ │ ├── Screenscraping_2.png │ │ ├── YouTube_better.png │ │ ├── YouTube_schematic2.png │ │ ├── YouTube_schematic2_harvesting.png │ │ └── YouTube_schematic2_scraping.png │ └── libs │ │ ├── clipboard │ │ └── clipboard.min.js │ │ ├── header-attrs-2.6 │ │ └── header-attrs.js │ │ ├── header-attrs │ │ └── header-attrs.js │ │ ├── remark-css-0.0.1 │ │ ├── default-fonts.css │ │ └── default.css │ │ ├── remark-css │ │ ├── default-fonts.css │ │ └── default.css │ │ ├── tile-view │ │ ├── tile-view.css │ │ └── tile-view.js │ │ ├── xaringanExtra-clipboard │ │ ├── xaringanExtra-clipboard.css │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ └── xaringanExtra-extra-styles.css ├── A3_Collecting_data_with_tuber │ ├── .gitignore │ ├── A3_Collecting_data_with_the_tuber_package_for_R.Rmd │ ├── A3_Collecting_data_with_the_tuber_package_for_R.html │ ├── A3_Collecting_data_with_the_tuber_package_for_R.pdf │ ├── images │ │ ├── 23.png │ │ ├── 24.png │ │ ├── 25.png │ │ ├── 26.png │ │ └── yt_api_key.png │ └── libs │ │ ├── clipboard │ │ └── clipboard.min.js │ │ ├── header-attrs │ │ └── header-attrs.js │ │ ├── kePrint │ │ └── kePrint.js │ │ ├── lightable │ │ └── lightable.css │ │ ├── remark-css │ │ ├── default-fonts.css │ │ └── default.css │ │ ├── tile-view │ │ ├── tile-view.css │ │ └── tile-view.js │ │ ├── twitter-widget │ │ └── widgets.js │ │ ├── xaringanExtra-clipboard │ │ ├── xaringanExtra-clipboard.css │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ └── xaringanExtra-extra-styles.css ├── A4_Processing_and_cleaning_user_comments │ ├── A4_Processing_and_Cleaning_User_Comments.Rmd │ ├── A4_Processing_and_Cleaning_User_Comments.html │ ├── A4_Processing_and_cleaning_user_comments.pdf │ ├── Images │ │ ├── DS1.jpg │ │ ├── DS2.jpg │ │ └── EmojiMovie.png │ ├── libs │ │ ├── clipboard │ │ │ └── clipboard.min.js │ │ ├── header-attrs │ │ │ └── header-attrs.js │ │ ├── remark-css │ │ │ ├── default-fonts.css │ │ │ └── default.css │ │ ├── tile-view │ │ │ ├── tile-view.css │ │ │ └── tile-view.js │ │ ├── xaringanExtra-clipboard │ │ │ ├── xaringanExtra-clipboard.css │ │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ │ └── xaringanExtra-extra-styles.css │ └── workshop.css ├── B1_Basic_Text_Analysis │ ├── B1_Basic_Text_Analysis.Rmd │ ├── B1_Basic_Text_Analysis.html │ ├── B1_Basic_Text_Analysis.pdf │ ├── B1_Basic_Text_Analysis_files │ │ └── figure-html │ │ │ ├── cloudy-plot-1.png │ │ │ ├── comments-over-time-plot-1.png │ │ │ ├── cool-emoji-plot-1.png │ │ │ ├── docfreq-plot-1.png │ │ │ ├── emoji-barplot-1.png │ │ │ └── word-freq-plot-1.png │ └── libs │ │ ├── clipboard │ │ └── clipboard.min.js │ │ ├── header-attrs │ │ └── header-attrs.js │ │ ├── remark-css │ │ ├── default-fonts.css │ │ └── default.css │ │ ├── tile-view │ │ ├── tile-view.css │ │ └── tile-view.js │ │ ├── twitter-widget │ │ └── widgets.js │ │ ├── xaringanExtra-clipboard │ │ ├── xaringanExtra-clipboard.css │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ └── xaringanExtra-extra-styles.css ├── B2_Sentiment_Analysis_of_User_Comments │ ├── B2_Sentiment_Analysis_of_User_Comments.Rmd │ ├── B2_Sentiment_Analysis_of_User_Comments.html │ ├── B2_Sentiment_Analysis_of_User_Comments.pdf │ ├── B2_Sentiment_Analysis_of_User_Comments_files │ │ └── figure-html │ │ │ ├── unnamed-chunk-10-1.png │ │ │ ├── unnamed-chunk-10-2.png │ │ │ ├── unnamed-chunk-11-1.png │ │ │ ├── unnamed-chunk-11-2.png │ │ │ ├── unnamed-chunk-12-1.png │ │ │ ├── unnamed-chunk-13-1.png │ │ │ ├── unnamed-chunk-14-1.png │ │ │ ├── unnamed-chunk-15-1.png │ │ │ ├── unnamed-chunk-16-1.png │ │ │ ├── unnamed-chunk-18-1.png │ │ │ ├── unnamed-chunk-20-1.png │ │ │ ├── unnamed-chunk-21-1.png │ │ │ ├── unnamed-chunk-22-1.png │ │ │ ├── unnamed-chunk-23-1.png │ │ │ ├── unnamed-chunk-24-1.png │ │ │ ├── unnamed-chunk-25-1.png │ │ │ ├── unnamed-chunk-26-1.png │ │ │ ├── unnamed-chunk-32-1.png │ │ │ ├── unnamed-chunk-34-1.png │ │ │ ├── unnamed-chunk-35-1.png │ │ │ ├── unnamed-chunk-36-1.png │ │ │ ├── unnamed-chunk-37-1.png │ │ │ ├── unnamed-chunk-8-1.png │ │ │ └── unnamed-chunk-9-1.png │ ├── Images │ │ ├── Emoji.png │ │ ├── EmojiMisconstruals.png │ │ ├── Encoding.png │ │ ├── SAExample.png │ │ └── all emoji differences.webp │ ├── libs │ │ ├── clipboard │ │ │ └── clipboard.min.js │ │ ├── header-attrs │ │ │ └── header-attrs.js │ │ ├── remark-css │ │ │ ├── default-fonts.css │ │ │ └── default.css │ │ ├── tile-view │ │ │ ├── tile-view.css │ │ │ └── tile-view.js │ │ ├── xaringanExtra-clipboard │ │ │ ├── xaringanExtra-clipboard.css │ │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ │ └── xaringanExtra-extra-styles.css │ └── workshop.css ├── B3_Excursus_Retrieving_Video_Subtitles │ ├── B3_Retrieving_Video_Subtitles.Rmd │ ├── B3_Retrieving_Video_Subtitles.html │ ├── B3_Retrieving_Video_Subtitles.pdf │ ├── Images │ │ └── youtubesubtitles.jpg │ └── libs │ │ ├── clipboard │ │ └── clipboard.min.js │ │ ├── header-attrs │ │ └── header-attrs.js │ │ ├── remark-css │ │ ├── default-fonts.css │ │ └── default.css │ │ ├── tile-view │ │ ├── tile-view.css │ │ └── tile-view.js │ │ ├── xaringanExtra-clipboard │ │ ├── xaringanExtra-clipboard.css │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ └── xaringanExtra-extra-styles.css ├── B4_Recap_Outlook_Practice │ ├── B4_Recap_Outlook_Practice.Rmd │ ├── B4_Recap_Outlook_Practice.html │ ├── B4_Recap_Outlook_Practice.pdf │ └── libs │ │ ├── clipboard │ │ └── clipboard.min.js │ │ ├── header-attrs │ │ └── header-attrs.js │ │ ├── remark-css │ │ ├── default-fonts.css │ │ └── default.css │ │ ├── tile-view │ │ ├── tile-view.css │ │ └── tile-view.js │ │ ├── xaringanExtra-clipboard │ │ ├── xaringanExtra-clipboard.css │ │ └── xaringanExtra-clipboard.js │ │ └── xaringanExtra-extra-styles │ │ └── xaringanExtra-extra-styles.css └── workshop.css ├── solutions ├── A2_The_Youtube_API_exercises.Rmd ├── A2_The_Youtube_API_exercises_solution.html ├── A3_tuber_exercises.Rmd ├── A3_tuber_exercises_solution.html ├── A4_Preprocessing_and_cleaning_data_exercises.Rmd ├── A4_Preprocessing_and_cleaning_data_exercises_solution.html ├── B1_Basic_text_analysis_exercises.Rmd ├── B1_Basic_text_analysis_exercises_solution.html ├── B2_Sentiment_analysis_of_user_comments_exercises.Rmd ├── B2_Sentiment_analysis_of_user_comments_exercises_solution.html └── images │ ├── APIRequests.png │ └── Quotas.png └── youtube-workshop-gesis-2022.Rproj /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | *_cache/ 32 | /cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | 38 | # R Environment Variables 39 | .Renviron 40 | 41 | # data folder 42 | data/* -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use these materials, please cite them as follows." 3 | authors: 4 | - family-names: "Breuer" 5 | given-names: "Johannes" 6 | orcid: "https://orcid.org/0000-0001-5906-7873" 7 | - family-names: "Kohne" 8 | given-names: "Julian" 9 | - family-names: "Mohseni" 10 | given-names: "M. Rohangis" 11 | orcid: "https://orcid.org/0000-0001-7686-8322" 12 | title: "Workshop Automatic Sampling and Analysis of YouTube Comments" 13 | date-released: 2022-02-21 14 | url: "https://github.com/jobreu/youtube-workshop-gesis-2022" 15 | license: CC-BY-4.0 16 | -------------------------------------------------------------------------------- /scripts/Authentication.R: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | # NB: Be careful to NOT share these credentials with anyone else whom you don't want to be able to use this account. 4 | appID <- "YourYouTubeClientID" # Insert your own Client ID here (OAuth 2.0 Client ID created for your project via the Google Developers platform) 5 | appSecret <- "YourYouTubeClientSecret" # Insert your own Client Secret here (Client key created for your project via the Google Developers platform) 6 | 7 | # Optional: authorization if you use the keyring package 8 | # creating the keyring 9 | library(keyring) 10 | keyring_create("YouTube", password = "YouTube") # This is the password of your keyring. You can replace it if you like. 11 | # saving your ID into the keyring 12 | appID <- "YourYouTubeClientID" # Insert your own Client ID here 13 | key_set_with_value("appSecret", appID, password = "YourYouTubeClientSecret", keyring = "YouTube") # insert your own Client Secret here 14 | keyring_unlock("YouTube") 15 | # getting the IDs from the keyring 16 | keyring_unlock(keyring = "YouTube", password = "YouTube") # Provide the password of the keyring here; alternatively, leave it empty here and you will be prompted for it 17 | appSecret <- key_get("appSecret", username = appID, keyring = "YouTube") -------------------------------------------------------------------------------- /scripts/CamelCase.R: -------------------------------------------------------------------------------- 1 | # CamelCase function 2 | 3 | ## convert emoji names to CamelCase (if you want to learn (more) about CamelCase: https://en.wikipedia.org/wiki/Camel_case) 4 | simpleCap <- function(x) { 5 | 6 | # Splitting the string 7 | splitted <- strsplit(x, " ")[[1]] 8 | 9 | # Pasting it back together with capital letters 10 | paste(toupper(substring(splitted, 1,1)), substring(splitted, 2),sep = "", collapse = " ") 11 | 12 | } -------------------------------------------------------------------------------- /scripts/ExtractEmoji.R: -------------------------------------------------------------------------------- 1 | # function for extracting Emoji from text 2 | 3 | ExtractEmoji <- function(x){ 4 | 5 | SpacerInsert <- gsub(" ","[{[SpAC0R]}]", x) 6 | ExtractEmoji <- rm_between(SpacerInsert,"EMOJI_","[{[SpAC0R]}]", fixed = TRUE, extract = TRUE, clean = FALSE, trim = FALSE, include.markers = TRUE) 7 | UnlistEmoji <- unlist(ExtractEmoji) 8 | DeleteSpacer <- sapply(UnlistEmoji,function(x){gsub("[{[SpAC0R]}]"," ",x,fixed = TRUE)}) 9 | names(DeleteSpacer) <- NULL 10 | 11 | Emoji <-paste0(DeleteSpacer,collapse = "") 12 | return(Emoji) 13 | 14 | } -------------------------------------------------------------------------------- /scripts/ReplaceEmoji.R: -------------------------------------------------------------------------------- 1 | # Function for replacing emojis in the comments 2 | 3 | ReplaceEmoji <- function(x) { 4 | 5 | # install packages 6 | 7 | if ("remotes" %in% installed.packages() != TRUE) { 8 | install.packages("remotes") 9 | } 10 | if ("qdapRegex" %in% installed.packages() != TRUE) { 11 | install.packages("qdapRegex") 12 | } 13 | if ("emo" %in% installed.packages() != TRUE) { 14 | remotes::install_github("hadley/emo") 15 | } 16 | 17 | # load packages 18 | library(qdapRegex) 19 | library(emo) 20 | 21 | # source helper functions 22 | source("CamelCase.R") 23 | 24 | # import emoji list 25 | EmojiList <- jis 26 | 27 | CamelCaseEmojis <- lapply(jis$name, simpleCap) 28 | CollapsedEmojis <- lapply(CamelCaseEmojis, function(x){gsub(" ", "", x, fixed = TRUE)}) 29 | EmojiList[,4]$name <- unlist(CollapsedEmojis) 30 | 31 | # order the list by the length of the string to avoid partial matching of shorter strings 32 | EmojiList <- EmojiList[rev(order(nchar(jis$emoji))),] 33 | 34 | # assign x to a new variable so we can save the progress in the for-loop (see below) 35 | New <- x 36 | 37 | # note: rm_default throws a warning on each iteration that we can ignore 38 | oldw <- getOption("warn") 39 | options(warn = -1) 40 | 41 | # cycle through the list and replace everything 42 | # we have to add clean = FALSE and trim = FALSE to avoid deleting whitespaces that are part of the pattern 43 | 44 | for (i in 1:dim(EmojiList)[1]){ 45 | 46 | New <- rm_default(New, pattern = EmojiList[i,3],replacement = paste0("EMOJI_", EmojiList[i,4]$name, " "), fixed = TRUE, clean = FALSE, trim = FALSE) 47 | 48 | } 49 | 50 | # turn warning messages back on 51 | options(warn = oldw) 52 | 53 | # output result 54 | return(New) 55 | 56 | } -------------------------------------------------------------------------------- /scripts/YouTubeAPIAccess.R: -------------------------------------------------------------------------------- 1 | #### NEVER SHARE THIS SCRIPT WITH YOUR CREDENTIALS STORED IN IT! 2 | 3 | 4 | #### Testing YouTube API Access 5 | 6 | # installing the tuber package if not installed already 7 | if("tuber" %in% installed.packages() != TRUE) { 8 | install.packages("tuber") 9 | } 10 | 11 | # loading tuber package 12 | library(Rcpp) # making extra sure this package is available 13 | library(tuber) 14 | 15 | 16 | # Your Credentials (NEVER SHARE THOSE!) 17 | ID <- "ENTER-API-ID-HERE" 18 | secret <- "ENTER-API-SECRET-HERE" 19 | 20 | # authentication 21 | yt_oauth(ID, secret, token ="") 22 | 23 | # You will be asked in the R-console to 24 | # save an access token: Select no 25 | 26 | # You will be sent back to your browser to log in 27 | 28 | ### testing if it works: 29 | 30 | # get statistics of https://www.youtube.com/watch?v=HluANRwPyNo 31 | get_stats(video_id="HluANRwPyNo") 32 | 33 | # Searching for specific keywords 34 | data <- yt_search(term = "web scraping R", 35 | published_after = "2019-01-01T00:00:00Z", 36 | published_before = "2021-01-01T00:00:00Z", 37 | simplify = TRUE) 38 | 39 | # Collecting comments 40 | comments <- get_all_comments(video_id = "iik25wqIuFo") 41 | commentThreads <- get_comment_threads(filter=c(video_id = "iik25wqIuFo")) -------------------------------------------------------------------------------- /scripts/emoji_mapping_function.R: -------------------------------------------------------------------------------- 1 | # Notes: 2 | # This function requires that the following packages are installed: 3 | # - dplyr, stringr (both part of the tidyverse) 4 | # - hadley/emo & dill/emoGG from GitHub 5 | # The function has two arguments: 6 | # x = the data frame with the frequencies 7 | # n = the number of mappings to create 8 | # The data frame containing the frequencies should be created using the `textstat_frequency()` function from the `quanteda.textstats` package 9 | # The emoji names in the `feature` column of the frequency df should have the format "emoji_facewithtearsofjoy" 10 | # Please note that this code has not been tested systematically. 11 | # Depending on which emojis are the most frequent for the video you look at, this function might not work because 12 | # a) one of the emojis is not included in the emoji lookup table (which uses the `jis` data frame from the `emo` package or 13 | # b) the content in the `runes` column does not match the format/code that the `emoji` argument in the `geom_emoji` function from the `emoGG` package expects. 14 | 15 | create_emoji_mappings <- function(x, n) { 16 | 17 | emoji_lookup <- emo::jis %>% 18 | dplyr::select(runes, name) %>% 19 | dplyr::mutate(runes = stringr::str_to_lower(runes), 20 | name = stringr::str_to_lower(name)) %>% 21 | dplyr::mutate(name = stringr::str_replace_all(name, " ", "")) %>% 22 | dplyr::mutate(name = paste0("emoji_", name)) 23 | 24 | library(emoGG) 25 | 26 | for(i in 1:n){ 27 | name <- paste0("mapping", i) 28 | assign(name, 29 | do.call(geom_emoji,list(data = x[i,], 30 | emoji = gsub("^0{2}","", strsplit(tolower(emoji_lookup$runes[emoji_lookup$name == as.character(x[i,]$feature)])," ")[[1]][1]))), envir = .GlobalEnv) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scripts/install_packages.R: -------------------------------------------------------------------------------- 1 | # check if packages are installed and install them if that is not the case 2 | packages <- c("remotes", 3 | "tuber", 4 | "tidyverse", 5 | "anytime", 6 | "qdapRegex", 7 | "vosonSML", 8 | "lexicon", 9 | "syuzhet", 10 | "ggcorrplot", 11 | "sentimentr", 12 | "quanteda", 13 | "quanteda.textstats", 14 | "wordcloud") 15 | 16 | install.packages(setdiff(packages, 17 | rownames(installed.packages()))) 18 | 19 | # some packages need to be installed from GitHub 20 | library(remotes) 21 | 22 | install_github("hadley/emo") 23 | install_github("dill/emoGG") 24 | install_github("jooyoungseo/youtubecaption") -------------------------------------------------------------------------------- /scripts/video_details_to_df.R: -------------------------------------------------------------------------------- 1 | # taken & adapted from the development version of tuber on Feb 22nd, 2022 (https://github.com/soodoku/tuber) 2 | # see this commit: https://github.com/soodoku/tuber/commit/42a8131098856c8aeb0fcbad70ed5f0e33b9652b 3 | 4 | conditional_unnest_wider <- function(data_input, var) { 5 | if (var %in% names(data_input)) { 6 | tidyr::unnest_wider(data_input, var, names_sep = "_") 7 | } else { 8 | data_input 9 | } 10 | } 11 | 12 | 13 | video_details_to_df <- function(res) { 14 | intermediate <- res %>% 15 | tibble::enframe() %>% 16 | tidyr::pivot_wider() %>% 17 | tidyr::unnest(cols = c(kind, etag)) %>% 18 | # reflect level of nesting in column name 19 | dplyr::rename(response_kind = kind, response_etag = etag) %>% 20 | tidyr::unnest(items) %>% 21 | tidyr::unnest_wider(col = items) %>% 22 | # reflect level of nesting in column name for those that may not be unique 23 | dplyr::rename(items_kind = kind, items_etag = etag) %>% 24 | tidyr::unnest_wider(snippet) 25 | 26 | intermediate_2 <- intermediate %>% 27 | # fields that may not be available: 28 | # live streaming details 29 | conditional_unnest_wider(var = "liveStreamingDetails") %>% 30 | # region restriction (rental videos) 31 | conditional_unnest_wider(var = "regionRestriction") %>% 32 | conditional_unnest_wider(var = "regionRestriction_allowed") %>% 33 | # statistics 34 | conditional_unnest_wider(var = "statistics") %>% 35 | # status 36 | conditional_unnest_wider(var = "status") %>% 37 | # player 38 | conditional_unnest_wider(var = "player") %>% 39 | # contentDetails 40 | conditional_unnest_wider(var = "contentDetails") %>% 41 | conditional_unnest_wider(var = "topicDetails") %>% 42 | conditional_unnest_wider(var = "localized") %>% 43 | conditional_unnest_wider(var = "pageInfo") %>% 44 | # thumbnails 45 | conditional_unnest_wider(var = "thumbnails") %>% 46 | conditional_unnest_wider(var = "thumbnails_default") %>% 47 | conditional_unnest_wider(var = "thumbnails_standard") %>% 48 | conditional_unnest_wider(var = "thumbnails_medium") %>% 49 | conditional_unnest_wider(var = "thumbnails_high") %>% 50 | conditional_unnest_wider(var = "thumbnails_maxres") 51 | 52 | 53 | intermediate_2 54 | } -------------------------------------------------------------------------------- /scripts/yt_parse.R: -------------------------------------------------------------------------------- 1 | ## YouTube parsing Script 2 | 3 | # Note: This function will only work if the helper functions are in the same directory 4 | # (CamelCase.R;ExtractEmoji.R;ReplaceEmoji.R) 5 | 6 | yt_parse <- function(data){ 7 | 8 | #### Setup 9 | 10 | # installing packages 11 | if ("remotes" %in% installed.packages() != TRUE) { 12 | install.packages("remotes") 13 | } 14 | if ("anytime" %in% installed.packages() != TRUE) { 15 | install.packages("anytime") 16 | } 17 | if ("qdapRegex" %in% installed.packages() != TRUE) { 18 | install.packages("qdapRegex") 19 | } 20 | if ("emo" %in% installed.packages() != TRUE) { 21 | remotes::install_github("hadley/emo") 22 | } 23 | 24 | # loading packages 25 | library(anytime) 26 | library(qdapRegex) 27 | library(emo) 28 | 29 | # sourcing helper functions 30 | source("CamelCase.R") 31 | source("ExtractEmoji.R") 32 | source("ReplaceEmoji.R") 33 | 34 | #### Data Preperation 35 | 36 | # accounting for dataframes without "parentId" column (those scraped with get_comments() instead of get_all_comments()) 37 | 38 | if (is.null(data$parentId)) { 39 | 40 | parentId <- rep(NA,dim(data)[1]) 41 | data <- cbind.data.frame(data,parentId) 42 | 43 | } 44 | 45 | # only keeping the relevant columns 46 | data <- data[,c("authorDisplayName","textOriginal","likeCount","publishedAt","updatedAt","parentId","id")] 47 | 48 | # convert dataframe columns to proper types 49 | data$authorDisplayName <- as.character(data$authorDisplayName) 50 | data$textOriginal <- as.character(data$textOriginal) 51 | data$likeCount <- as.numeric(data$likeCount) 52 | data$parentId <- as.character(data$parentId) 53 | data$id <- as.character(data$id) 54 | data$publishedAt <- anytime(data$publishedAt, asUTC = TRUE) 55 | data$updatedAt <- anytime(data$updatedAt, asUTC = TRUE) 56 | 57 | #### Emojis 58 | 59 | # Create a text column in which emojis are replaced by their textual descriptions 60 | # (This is handled by the helper function ReplaceEmoji) 61 | TextEmoRep <- ReplaceEmoji(data$textOriginal) 62 | 63 | # Create a text column in which emojis are deleted 64 | TextEmoDel <- ji_replace_all(data$textOriginal,"") 65 | 66 | # Create a column with only the textual descriptions of emojis for each comment 67 | Emoji <- sapply(TextEmoRep,ExtractEmoji) 68 | 69 | #### URLs 70 | 71 | # Extract URLs from comments 72 | Links <- rm_url(data$textOriginal, extract = TRUE) 73 | Links <- I(Links) 74 | 75 | #### Combine everything into one dataframe 76 | df <- cbind.data.frame(data$authorDisplayName,data$textOriginal,TextEmoRep,TextEmoDel,Emoji,data$likeCount,Links,data$publishedAt,data$updatedAt,data$parentId,data$id, stringsAsFactors = FALSE) 77 | names(df) <- c("Author","Text","TextEmojiReplaced","TextEmojiDeleted","Emoji","LikeCount","URL","Published","Updated","ParentId","CommentID") 78 | row.names(df) <- NULL 79 | 80 | #### return results 81 | return(df) 82 | 83 | } -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/A0_YouTubeAPISetup.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/A0_YouTubeAPISetup.pdf -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/0.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/1.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/10.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/11.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/12.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/13.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/14.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/15.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/16.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/17.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/18.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/19.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/2.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/20.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/21.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/22.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/23.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/24.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/25.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/26.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/27.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/3.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/4.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/5.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/6.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/7.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/8.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/Images/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobreu/youtube-workshop-gesis-2022/a5ff2a9e846ba359fab8c6313fda104606d0e960/slides/A0_YouTube_API_Setup/Images/9.png -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/libs/header-attrs/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/libs/remark-css/default-fonts.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); 2 | @import url(https://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic); 3 | @import url(https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700); 4 | 5 | body { font-family: 'Droid Serif', 'Palatino Linotype', 'Book Antiqua', Palatino, 'Microsoft YaHei', 'Songti SC', serif; } 6 | h1, h2, h3 { 7 | font-family: 'Yanone Kaffeesatz'; 8 | font-weight: normal; 9 | } 10 | .remark-code, .remark-inline-code { font-family: 'Source Code Pro', 'Lucida Console', Monaco, monospace; } 11 | -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/libs/remark-css/default.css: -------------------------------------------------------------------------------- 1 | a, a > code { 2 | color: rgb(249, 38, 114); 3 | text-decoration: none; 4 | } 5 | .footnote { 6 | position: absolute; 7 | bottom: 3em; 8 | padding-right: 4em; 9 | font-size: 90%; 10 | } 11 | .remark-code-line-highlighted { background-color: #ffff88; } 12 | 13 | .inverse { 14 | background-color: #272822; 15 | color: #d6d6d6; 16 | text-shadow: 0 0 20px #333; 17 | } 18 | .inverse h1, .inverse h2, .inverse h3 { 19 | color: #f3f3f3; 20 | } 21 | /* Two-column layout */ 22 | .left-column { 23 | color: #777; 24 | width: 20%; 25 | height: 92%; 26 | float: left; 27 | } 28 | .left-column h2:last-of-type, .left-column h3:last-child { 29 | color: #000; 30 | } 31 | .right-column { 32 | width: 75%; 33 | float: right; 34 | padding-top: 1em; 35 | } 36 | .pull-left { 37 | float: left; 38 | width: 47%; 39 | } 40 | .pull-right { 41 | float: right; 42 | width: 47%; 43 | } 44 | .pull-right + * { 45 | clear: both; 46 | } 47 | img, video, iframe { 48 | max-width: 100%; 49 | } 50 | blockquote { 51 | border-left: solid 5px lightgray; 52 | padding-left: 1em; 53 | } 54 | .remark-slide table { 55 | margin: auto; 56 | border-top: 1px solid #666; 57 | border-bottom: 1px solid #666; 58 | } 59 | .remark-slide table thead th { border-bottom: 1px solid #ddd; } 60 | th, td { padding: 5px; } 61 | .remark-slide thead, .remark-slide tfoot, .remark-slide tr:nth-child(even) { background: #eee } 62 | 63 | @page { margin: 0; } 64 | @media print { 65 | .remark-slide-scaler { 66 | width: 100% !important; 67 | height: 100% !important; 68 | transform: scale(1) !important; 69 | top: 0 !important; 70 | left: 0 !important; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/libs/tile-view/tile-view.css: -------------------------------------------------------------------------------- 1 | .remark__tile-view * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .remark__tile-view { 6 | background: lightgray; 7 | position: relative; 8 | width: 100%; 9 | height: 100%; 10 | padding: 3em; 11 | font-size: 18px; 12 | box-sizing: border-box; 13 | overflow: scroll; 14 | } 15 | 16 | .remark__tile-view__header { 17 | text-align: center; 18 | } 19 | 20 | .remark__tile-view__tiles { 21 | display: grid; 22 | /* Set column width in JS */ 23 | /* grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); */ 24 | justify-items: center; 25 | } 26 | 27 | .remark__tile-view__tile { 28 | position: relative; 29 | margin: 0.5em; 30 | padding: 0.5em; 31 | } 32 | 33 | .remark__tile-view__slide-container { 34 | margin: 0 auto; 35 | } 36 | 37 | .remark__tile-view__tile--current { 38 | background: #ffd863; 39 | border: 5px solid #ffd863; 40 | margin: calc(0.5em - 5px); 41 | border-radius: 0; 42 | } 43 | 44 | .remark__tile-view__tile--seen { 45 | opacity: 0.5; 46 | } 47 | 48 | .remark__tile-view__tile:hover { 49 | /* background: #993d70; */ 50 | background: #44bc96; 51 | opacity: 1; 52 | } 53 | -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/libs/tile-view/tile-view.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Tile View for remark.js Slides 3 | * 4 | * Garrick Aden-Buie 5 | * 6 | * Inspired and converted to Vanilla JS from 7 | * https://github.com/StephenHesperus/remark-hook/ 8 | * 9 | * Include after remarkjs slides are initialized. 10 | * 11 | */ 12 | 13 | /* global slideshow */ 14 | (function () { 15 | const ready = function (fn) { 16 | /* MIT License Copyright (c) 2016 Nuclei */ 17 | /* https://github.com/nuclei/readyjs */ 18 | const completed = () => { 19 | document.removeEventListener('DOMContentLoaded', completed) 20 | window.removeEventListener('load', completed) 21 | fn() 22 | } 23 | if (document.readyState !== 'loading') { 24 | setTimeout(fn) 25 | } else { 26 | document.addEventListener('DOMContentLoaded', completed) 27 | window.addEventListener('load', completed) 28 | } 29 | } 30 | 31 | ready(function () { 32 | const launchKey = 79 // keycode for O, used to enable tile view 33 | 34 | // Slides container 35 | const remarkSlideShow = document.querySelector('div.remark-slides-area') 36 | 37 | let tileView = document.querySelector('div.remark__tile-view') 38 | if (!tileView) { 39 | tileView = document.createElement('div') 40 | tileView.className = 'remark__tile-view' 41 | } 42 | 43 | const toggleElement = el => { 44 | el.style.display = el.style.display === 'none' ? '' : 'none' 45 | } 46 | 47 | function slideshowResize () { 48 | window.dispatchEvent(new Event('resize')) 49 | } 50 | 51 | const toggleTileView = function () { 52 | toggleElement(tileView) 53 | toggleElement(remarkSlideShow) 54 | 55 | if (tileView.style.display === 'none') { 56 | // tileView is now hidden, go to current slide 57 | slideshow.gotoSlide(tileVars.currentSlideIdx + 1) 58 | 59 | slideshow.resume() 60 | slideshowResize() 61 | } else { 62 | // store current slide index prior to launching tile-view 63 | tileVars.currentSlideIdx = slideshow.getCurrentSlideIndex() 64 | 65 | // set class on seen and current slide and scroll into view 66 | const tiles = tileView.querySelectorAll('.remark__tile-view__tile'); 67 | [...tiles].forEach((tile, idx) => { 68 | tile.classList.toggle( 69 | 'remark__tile-view__tile--seen', 70 | idx < tileVars.currentSlideIdx 71 | ) 72 | tile.classList.toggle( 73 | 'remark__tile-view__tile--current', 74 | idx === tileVars.currentSlideIdx 75 | ) 76 | }) 77 | tiles[tileVars.currentSlideIdx].scrollIntoView({ 78 | behavior: 'smooth', 79 | block: 'center' 80 | }) 81 | 82 | slideshow.pause() 83 | } 84 | } 85 | 86 | const createTileView = ({ minSize = 250, title = document.title } = {}) => { 87 | // Tile view header 88 | const h1 = document.createElement('h1') 89 | h1.className = 'remark__tile-view__header' 90 | h1.innerHTML = title 91 | 92 | tileView.appendChild(h1) 93 | const tiles = document.createElement('div') 94 | tiles.className = 'remark__tile-view__tiles' 95 | tileView.appendChild(tiles) 96 | 97 | // Clone slideshow 98 | const slidesArea = remarkSlideShow.cloneNode(true) 99 | 100 | // Calculate slide scale and tile container size 101 | const slideScaler = slidesArea.querySelector('.remark-slide-scaler') 102 | const slideWidth = parseFloat(slideScaler.style.width.replace('px', '')) 103 | const slideHeight = parseFloat( 104 | slideScaler.style.height.replace('px', '') 105 | ) 106 | const scale = minSize / Math.min(slideWidth, slideHeight) 107 | let tileWidth = Math.round(slideWidth * scale) 108 | let tileHeight = Math.round(slideHeight * scale) 109 | 110 | // convert tileWidth/Height to em relative to base 18px (set in CSS) 111 | tileWidth = tileWidth / 18 112 | tileHeight = tileHeight / 18 113 | 114 | tiles.style.gridTemplateColumns = `repeat(auto-fill, minmax(${tileWidth}em, 1fr))` 115 | 116 | const slides = slidesArea.querySelectorAll('.remark-slide-container') 117 | 118 | slides.forEach((slide, slideIndex) => { 119 | let tile = document.createElement('template') 120 | tile.innerHTML = `
121 |
122 |
` 123 | tile = tile.content.firstChild 124 | 125 | const tileContainer = tile.querySelector( 126 | '.remark__tile-view__slide-container' 127 | ) 128 | tileContainer.style.width = `${tileWidth}em` 129 | tileContainer.style.height = `${tileHeight}em` 130 | 131 | const thisSlideScaler = slide.querySelector('.remark-slide-scaler') 132 | thisSlideScaler.style.top = '0px' 133 | thisSlideScaler.style.left = '0px' 134 | thisSlideScaler.style.transform = `scale(${scale})` 135 | thisSlideScaler.parentElement.classList.add('remark-visible') 136 | 137 | slide.addEventListener('click', () => { 138 | tileVars.currentSlideIdx = slideIndex 139 | toggleTileView() 140 | }) 141 | 142 | tileContainer.appendChild(slide) 143 | tiles.appendChild(tile) 144 | }) 145 | 146 | document.body.appendChild(tileView) 147 | } 148 | 149 | const tileVars = {} 150 | 151 | document.addEventListener('keydown', ev => { 152 | if (ev.keyCode === launchKey) { 153 | toggleTileView() 154 | } 155 | }) 156 | 157 | const addTileViewHelpText = () => { 158 | const helpTable = document.querySelector( 159 | '.remark-help-content table.light-keys' 160 | ) 161 | if (!helpTable) { 162 | console.error( 163 | 'Could not find remark help table, has remark been initialized?' 164 | ) 165 | return 166 | } 167 | const newRow = document.createElement('tr') 168 | newRow.innerHTML += 'o' 169 | newRow.innerHTML += 'Tile View: Overview of Slides' 170 | helpTable.append(newRow) 171 | } 172 | 173 | createTileView({ minSize: 200 }) 174 | toggleElement(tileView) 175 | addTileViewHelpText() 176 | }) 177 | })() 178 | -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/libs/xaringanExtra-clipboard/xaringanExtra-clipboard.css: -------------------------------------------------------------------------------- 1 | .xaringanextra-clipboard-button { 2 | position: absolute; 3 | top: 0; 4 | right: 0; 5 | font-size: 0.8em; 6 | padding: 0.5em; 7 | display: none; 8 | background-color: transparent; 9 | border: none; 10 | opacity: 0.5; 11 | border-radius: 0; 12 | } 13 | 14 | .xaringanextra-clipboard-button:hover { 15 | background-color: rgba(0, 0, 0, 0.1); 16 | border: none; 17 | opacity: 1; 18 | } 19 | 20 | :hover > .xaringanextra-clipboard-button { 21 | display: block; 22 | transform: translateY(0); 23 | } 24 | -------------------------------------------------------------------------------- /slides/A0_YouTube_API_Setup/libs/xaringanExtra-clipboard/xaringanExtra-clipboard.js: -------------------------------------------------------------------------------- 1 | /* global slideshow,window,document */ 2 | window.xaringanExtraClipboard = function (selector, text) { 3 | if (!window.ClipboardJS.isSupported()) return 4 | if (!window.xaringanExtraClipboards) window.xaringanExtraClipboards = {} 5 | 6 | const ready = function (fn) { 7 | /* MIT License Copyright (c) 2016 Nuclei */ 8 | /* https://github.com/nuclei/readyjs */ 9 | const completed = () => { 10 | document.removeEventListener('DOMContentLoaded', completed) 11 | window.removeEventListener('load', completed) 12 | fn() 13 | } 14 | if (document.readyState !== 'loading') { 15 | setTimeout(fn) 16 | } else { 17 | document.addEventListener('DOMContentLoaded', completed) 18 | window.addEventListener('load', completed) 19 | } 20 | } 21 | 22 | ready(function () { 23 | const { 24 | button: buttonText = 'Copy Code', 25 | success: successText = 'Copied!', 26 | error: errorText = 'Press Ctrl+C to Copy' 27 | } = text 28 | 29 | const template = '` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert ` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert ` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert ` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert ` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert ` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert ` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert ` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert ` 31 | 32 | const isRemarkSlideshow = typeof slideshow !== 'undefined' && 33 | Object.prototype.hasOwnProperty.call(slideshow, 'getSlides') 34 | 35 | let siblingSelector = selector || 'pre' 36 | if (!selector && isRemarkSlideshow) { 37 | siblingSelector = '.remark-slides-area ' + siblingSelector 38 | } 39 | 40 | // insert