From 207fe616090176fdb4c4054b2ab369fc108337f6 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 17:49:39 -0300 Subject: [PATCH 01/33] Set Jekyll Page --- app.js | 221 ---------------------------------------- callback.html | 163 ------------------------------ fuzzyset.js | 275 -------------------------------------------------- index.html | 59 ----------- wordcache.js | 89 ---------------- 5 files changed, 807 deletions(-) delete mode 100644 app.js delete mode 100644 callback.html delete mode 100644 fuzzyset.js delete mode 100644 index.html delete mode 100644 wordcache.js diff --git a/app.js b/app.js deleted file mode 100644 index 4bf6348..0000000 --- a/app.js +++ /dev/null @@ -1,221 +0,0 @@ -(function(exports) { - - var cache = new WordCache(); - - var g_name = ''; - var g_tracks = ''; - - function setStatus(text) { - if (text != '') { - $('#status').html( - '
' + - '
' + - text + - '
' + - '
' - ); - } else { - $('#status').html(''); - } - } - - var Playlist = function() { - } - - var splitText = function(inputtext) { - var words = inputtext - .split(/[ \n\r\t]/) - .map(function(w) { return w.toLowerCase().trim().replace(/^[.,-]+/,'').replace(/[.,-]+$/g,''); }) - .filter(function(w) { return (w.length > 0); }); - words = words.slice(0, 100); - return words; - } - - var refreshText = function() { - setStatus('Updating text...'); - - g_name = $('#alltext').val().trim(); - var words = splitText(g_name); - console.log('text changed.', g_name, words); - cache.lookupWords(words, function(worddata) { - - setStatus(''); - - console.log('wordcache callback', worddata); - // $('#debug').text(JSON.stringify(worddata, null, 2)); - - var txt = ''; - g_tracks = []; - worddata.forEach(function(data) { - console.log('word', data.word); - - data.tracks.sort(function(a,b) { - return Math.random() - 0.5; - }) - - var names = data.tracks.map(function(track) { - return track.name; - }); - - console.log('names', names); - - var f = new FuzzySet(names); - var fr = f.get(data.word); - console.log('fr', fr); - - var found = null; - var title = ''; - - if (!found) { - data.tracks.forEach(function(track) { - if (track.name.toLowerCase().trim() === data.word.toLowerCase().trim()) { - found = track; - } - }); - } - - if (!found) { - if (fr && fr.length > 0) { - data.tracks.forEach(function(track) { - if (track.name === fr[0][1]) { - found = track; - } - }); - } - } - - if (!found) { - if (data.tracks.length > 0) { - found = data.tracks[0]; - } - } - - console.log('found', found); - if (found) { - g_tracks.push(found.uri); - txt += '
' + - '' + - '' + - '
\n'; - } else { - txt += '
No match found for the word "' + data.word+ '"
\n' - } - }); - - $('#debug').html(txt); - }); - } - - var doSearch = function(word, callback) { - console.log('search for ' + word); - var url = 'https://api.spotify.com/v1/search?type=track&limit=50&q=' + encodeURIComponent('track:"'+word+'"'); - $.ajax(url, { - dataType: 'json', - success: function(r) { - console.log('got track', r); - callback({ - word: word, - tracks: r.tracks.items - .map(function(item) { - var ret = { - name: item.name, - artist: 'Unknown', - artist_uri: '', - album: item.album.name, - album_uri: item.album.uri, - cover_url: '', - uri: item.uri - } - if (item.artists.length > 0) { - ret.artist = item.artists[0].name; - ret.artist_uri = item.artists[0].uri; - } - if (item.album.images.length > 0) { - ret.cover_url = item.album.images[item.album.images.length - 1].url; - } - return ret; - }) - }); - }, - error: function(r) { - callback({ - word: word, - tracks: [] - }); - } - }); - } - - var resolveOneWord = function() { - var word = cache.pop(); - if (word) { - console.log('time to resolve word', word); - setStatus('Looking up the word "' + word + '"'); - doSearch(word, function(result) { - console.log('got word result', result); - cache.store(word, result); - setTimeout(resolveOneWord, 1); - }); - } else { - console.log('no words to look up...'); - setTimeout(resolveOneWord, 1000); - } - } - - var g_access_token = ''; - var g_username = ''; - - var client_id = ''; - var redirect_uri = ''; - - - if (location.host == 'localhost:8000') { - client_id = 'd37a9e88667b4fb3bc994299de2a52bd'; - redirect_uri = 'http://localhost:8000/callback.html'; - } else { - client_id = '6f9391eff32647baa44b1a700ad4a7fc'; - redirect_uri = 'http://lab.possan.se/playlistcreator-example/callback.html'; - } - - var doLogin = function(callback) { - var url = 'https://accounts.spotify.com/authorize?client_id=' + client_id + - '&response_type=token' + - '&scope=playlist-read-private%20playlist-modify%20playlist-modify-private' + - '&redirect_uri=' + encodeURIComponent(redirect_uri); - localStorage.setItem('createplaylist-tracks', JSON.stringify(g_tracks)); - localStorage.setItem('createplaylist-name', g_name); - var w = window.open(url, 'asdf', 'WIDTH=400,HEIGHT=500'); - } - - var refreshtimer = 0; - var queueRefreshText = function() { - if (refreshtimer) { - clearTimeout(refreshtimer); - } - refreshtimer = setTimeout(function() { - refreshText(); - }, 1000); - } - - exports.startApp = function() { - setStatus(''); - console.log('start app.'); - $('#alltext').keyup(function() { - queueRefreshText(); - }); - $('#alltext').change(function() { - queueRefreshText(); - }); - $('#start').click(function() { - doLogin(function() {}); - }) - $('#alltext').text('hello world'); - refreshText(); - resolveOneWord(); -} - -})(window); diff --git a/callback.html b/callback.html deleted file mode 100644 index d95bc33..0000000 --- a/callback.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - - - -
-
-
-

Creating playlist...

-
- -
-
- - diff --git a/fuzzyset.js b/fuzzyset.js deleted file mode 100644 index 1ab373f..0000000 --- a/fuzzyset.js +++ /dev/null @@ -1,275 +0,0 @@ -// from https://github.com/Glench/fuzzyset.js - -var FuzzySet = function(arr, useLevenshtein, gramSizeLower, gramSizeUpper) { - var fuzzyset = { - version: '0.0.1' - }; - - // default options - arr = arr || []; - fuzzyset.gramSizeLower = gramSizeLower || 2; - fuzzyset.gramSizeUpper = gramSizeUpper || 3; - fuzzyset.useLevenshtein = useLevenshtein || true; - - // define all the object functions and attributes - fuzzyset.exactSet = {} - fuzzyset.matchDict = {}; - fuzzyset.items = {}; - - // helper functions - var levenshtein = function(str1, str2) { - var current = [], prev, value; - - for (var i = 0; i <= str2.length; i++) - for (var j = 0; j <= str1.length; j++) { - if (i && j) - if (str1.charAt(j - 1) === str2.charAt(i - 1)) - value = prev; - else - value = Math.min(current[j], current[j - 1], prev) + 1; - else - value = i + j; - - prev = current[j]; - current[j] = value; - } - - return current.pop(); - }; - - // return an edit distance from 0 to 1 - var _distance = function(str1, str2) { - if (str1 == null && str2 == null) throw 'Trying to compare two null values' - if (str1 == null || str2 == null) return 0; - str1 = String(str1); str2 = String(str2); - - var distance = levenshtein(str1, str2); - if (str1.length > str2.length) { - return 1 - distance / str1.length; - } else { - return 1 - distance / str2.length; - } - }; - var _nonWordRe = /[^\w, ]+/; - - var _iterateGrams = function(value, gramSize) { - gramSize = gramSize || 2; - var simplified = '-' + value.toLowerCase().replace(_nonWordRe, '') + '-', - lenDiff = gramSize - simplified.length, - results = []; - if (lenDiff > 0) { - for (var i = 0; i < lenDiff; ++i) { - value += '-'; - } - } - for (var i = 0; i < simplified.length - gramSize + 1; ++i) { - results.push(simplified.slice(i, i + gramSize)) - } - return results; - }; - - var _gramCounter = function(value, gramSize) { - gramSize = gramSize || 2; - var result = {}, - grams = _iterateGrams(value, gramSize), - i = 0; - for (i; i < grams.length; ++i) { - if (grams[i] in result) { - result[grams[i]] += 1; - } else { - result[grams[i]] = 1; - } - } - return result; - }; - - // the main functions - fuzzyset.get = function(value, defaultValue) { - var result = this._get(value); - if (!result && defaultValue) { - return defaultValue; - } - return result; - }; - - fuzzyset._get = function(value) { - var normalizedValue = this._normalizeStr(value), - result = this.exactSet[normalizedValue]; - if (result) { - return [[1, result]]; - } - var results = []; - for (var gramSize = this.gramSizeUpper; gramSize > this.gramSizeLower; --gramSize) { - results = this.__get(value, gramSize); - if (results) { - return results; - } - } - return null; - }; - - fuzzyset.__get = function(value, gramSize) { - var normalizedValue = this._normalizeStr(value), - matches = {}, - gramCounts = _gramCounter(normalizedValue, gramSize), - items = this.items[gramSize], - sumOfSquareGramCounts = 0, - gram, - gramCount, - i, - index, - otherGramCount; - - for (gram in gramCounts) { - gramCount = gramCounts[gram]; - sumOfSquareGramCounts += Math.pow(gramCount, 2); - if (gram in this.matchDict) { - for (i = 0; i < this.matchDict[gram].length; ++i) { - index = this.matchDict[gram][i][0]; - otherGramCount = this.matchDict[gram][i][1]; - if (index in matches) { - matches[index] += gramCount * otherGramCount; - } else { - matches[index] = gramCount * otherGramCount; - } - } - } - } - - function isEmptyObject(obj) { - for(var prop in obj) { - if(obj.hasOwnProperty(prop)) - return false; - } - return true; - } - - if (isEmptyObject(matches)) { - return null; - } - - var vectorNormal = Math.sqrt(sumOfSquareGramCounts), - results = [], - matchScore; - // build a results list of [score, str] - for (var matchIndex in matches) { - matchScore = matches[matchIndex]; - results.push([matchScore / (vectorNormal * items[matchIndex][0]), items[matchIndex][1]]); - } - var sortDescending = function(a, b) { - if (a[0] < b[0]) { - return 1; - } else if (a[0] > b[0]) { - return -1; - } else { - return 0; - } - }; - results.sort(sortDescending); - if (this.useLevenshtein) { - var newResults = [], - endIndex = Math.min(50, results.length); - // truncate somewhat arbitrarily to 50 - for (var i = 0; i < endIndex; ++i) { - newResults.push([_distance(results[i][1], normalizedValue), results[i][1]]); - } - results = newResults; - results.sort(sortDescending); - } - var newResults = []; - for (var i = 0; i < results.length; ++i) { - if (results[i][0] == results[0][0]) { - newResults.push([results[i][0], this.exactSet[results[i][1]]]); - } - } - return newResults; - }; - - fuzzyset.add = function(value) { - var normalizedValue = this._normalizeStr(value); - if (normalizedValue in this.exactSet) { - return false; - } - - var i = this.gramSizeLower; - for (i; i < this.gramSizeUpper + 1; ++i) { - this._add(value, i); - } - }; - - fuzzyset._add = function(value, gramSize) { - var normalizedValue = this._normalizeStr(value), - items = this.items[gramSize] || [], - index = items.length; - - items.push(0); - var gramCounts = _gramCounter(normalizedValue, gramSize), - sumOfSquareGramCounts = 0, - gram, gramCount; - for (var gram in gramCounts) { - gramCount = gramCounts[gram]; - sumOfSquareGramCounts += Math.pow(gramCount, 2); - if (gram in this.matchDict) { - this.matchDict[gram].push([index, gramCount]); - } else { - this.matchDict[gram] = [[index, gramCount]]; - } - } - var vectorNormal = Math.sqrt(sumOfSquareGramCounts); - items[index] = [vectorNormal, normalizedValue]; - this.items[gramSize] = items; - this.exactSet[normalizedValue] = value; - }; - - fuzzyset._normalizeStr = function(str) { - if (Object.prototype.toString.call(str) !== '[object String]') throw 'Must use a string as argument to FuzzySet functions' - return str.toLowerCase(); - }; - - // return length of items in set - fuzzyset.length = function() { - var count = 0, - prop; - for (prop in this.exactSet) { - if (this.exactSet.hasOwnProperty(prop)) { - count += 1; - } - } - return count; - }; - - // return is set is empty - fuzzyset.isEmpty = function() { - for (var prop in this.exactSet) { - if (this.exactSet.hasOwnProperty(prop)) { - return false; - } - } - return true; - }; - - // return list of values loaded into set - fuzzyset.values = function() { - var values = [], - prop; - for (prop in this.exactSet) { - if (this.exactSet.hasOwnProperty(prop)) { - values.push(this.exactSet[prop]) - } - } - return values; - }; - - - // initialization - var i = fuzzyset.gramSizeLower; - for (i; i < fuzzyset.gramSizeUpper + 1; ++i) { - fuzzyset.items[i] = []; - } - // add all the items to the set - for (i = 0; i < arr.length; ++i) { - fuzzyset.add(arr[i]); - } - - return fuzzyset; -}; \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 3723381..0000000 --- a/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - -
-
-

Spotify WebAPI Playlist creation example

-

- Browse the sourcecode on GitHub, - Example by @possan -

-
-
- -
- -
-
-

Write your text here

-
- -
-

- -

-
-
-
-

Corresponding track names

-
-
-
-
- - \ No newline at end of file diff --git a/wordcache.js b/wordcache.js deleted file mode 100644 index db7bc6f..0000000 --- a/wordcache.js +++ /dev/null @@ -1,89 +0,0 @@ -(function(exports) { - -var WordCache = function() { - this.cachedwords = []; - this.queue = []; - this.data = JSON.parse(localStorage.getItem('wordcache') || '{}'); - this.listeners = []; -} - -WordCache.prototype.pop = function() { - if (this.queue.length == 0) { - return null; - } - - var item = this.queue[0]; - this.queue.splice(0, 1); - return item; -} - -WordCache.prototype.saveCache = function() { - localStorage.setItem('wordcache', JSON.stringify(this.data)); -} - -WordCache.prototype.store = function(word, data) { - console.log('store', word, data); - if (this.cachedwords.indexOf(word) == -1) { - this.cachedwords.push(word); - } - var idx = this.queue.indexOf(word); - if (idx != -1) { - this.queue.splice(idx, 1); - } - this.data[word] = data; - this.callFulfilledListeners(); - this.saveCache(); -} - -WordCache.prototype.callFulfilledListeners = function() { - console.log('callFulfilledListeners', this.listeners); - // callback(words); - var _this = this; - this.listeners.forEach(function(item) { - console.log('check listener', item); - - var anymissing = false; - var result = []; - for(var i=0; i Date: Tue, 22 Feb 2022 17:51:33 -0300 Subject: [PATCH 02/33] Create Jekyll --- _config.yml | 58 +++++++++ _layouts/default.html | 45 +++++++ assets/css/main.css | 9 ++ assets/js/app.js | 221 +++++++++++++++++++++++++++++++++ assets/js/fuzzyset.js | 275 +++++++++++++++++++++++++++++++++++++++++ assets/js/main.js | 112 +++++++++++++++++ assets/js/wordcache.js | 89 +++++++++++++ callback.md | 20 +++ index.md | 19 +++ 9 files changed, 848 insertions(+) create mode 100644 _config.yml create mode 100644 _layouts/default.html create mode 100644 assets/css/main.css create mode 100644 assets/js/app.js create mode 100644 assets/js/fuzzyset.js create mode 100644 assets/js/main.js create mode 100644 assets/js/wordcache.js create mode 100644 callback.md create mode 100644 index.md diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..2faec3b --- /dev/null +++ b/_config.yml @@ -0,0 +1,58 @@ +name: Playlist Generator +title: Playlist Generator +description: Create your Spotify Playlist +baseurl: + +include: ["_pages"] +permalink: /:title/ + +# Plugins +plugins: + - jekyll-feed + - jekyll-sitemap + - jekyll-paginate + - jekyll-seo-tag + - jekyll-pug + +authors: + milla: + name: Camila L. Oliveira + site: https://camilaloliveira.com.br + avatar: media/images/avatar_milla.png + bio: "Tecnóloga em Análise e Desenvolvimento de Sistemas, Bacharel em Administração Pública, Programadora de Computadores e Dispositivos Móveis e Técnica em Informática para Internet. + Com experiências nas empresas TCS e BRQ, atuando no desenvolvimento de soluções aos clientes há mais de 2 anos." + email: ola@camilaloliveira.com.br + twitter: https://twitter.com/millaloliveira + linkedin: https://linkedin.com/in/clcmo + facebook: https://facebook.com/millaloliveira + + +# Defaults +defaults: + + # all posts + - scope: + path: "_posts" + values: + layout: post + author: milla + avatar: /media/images/avatar_milla.png + + # all pages + - scope: + path: "_pages" + values: + layout: page + +# Syntax +markdown: kramdown +highlighter: rouge + +# Paginate +paginate: 10 + + + +# Exclude metadata and development time dependencies (like Grunt plugins) +exclude: [README.markdown, package.json, grunt.js, Gruntfile.js, Gruntfile.coffee, node_modules] + diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..db5bc0e --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,45 @@ + + + + + + + + + {{page.title}} + + + + + + + +
+
+
+

+ Playlist Generator +

+

+ Generated by Json +

+
+
+
+
+ {{content}} +
+ + + + + + + + \ No newline at end of file diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..6b2feb4 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,9 @@ +.column left { + position: absolute; + width: 50%; + } + + .column right { + position: absolute; + width: 50%; +} \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..4bf6348 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,221 @@ +(function(exports) { + + var cache = new WordCache(); + + var g_name = ''; + var g_tracks = ''; + + function setStatus(text) { + if (text != '') { + $('#status').html( + '
' + + '
' + + text + + '
' + + '
' + ); + } else { + $('#status').html(''); + } + } + + var Playlist = function() { + } + + var splitText = function(inputtext) { + var words = inputtext + .split(/[ \n\r\t]/) + .map(function(w) { return w.toLowerCase().trim().replace(/^[.,-]+/,'').replace(/[.,-]+$/g,''); }) + .filter(function(w) { return (w.length > 0); }); + words = words.slice(0, 100); + return words; + } + + var refreshText = function() { + setStatus('Updating text...'); + + g_name = $('#alltext').val().trim(); + var words = splitText(g_name); + console.log('text changed.', g_name, words); + cache.lookupWords(words, function(worddata) { + + setStatus(''); + + console.log('wordcache callback', worddata); + // $('#debug').text(JSON.stringify(worddata, null, 2)); + + var txt = ''; + g_tracks = []; + worddata.forEach(function(data) { + console.log('word', data.word); + + data.tracks.sort(function(a,b) { + return Math.random() - 0.5; + }) + + var names = data.tracks.map(function(track) { + return track.name; + }); + + console.log('names', names); + + var f = new FuzzySet(names); + var fr = f.get(data.word); + console.log('fr', fr); + + var found = null; + var title = ''; + + if (!found) { + data.tracks.forEach(function(track) { + if (track.name.toLowerCase().trim() === data.word.toLowerCase().trim()) { + found = track; + } + }); + } + + if (!found) { + if (fr && fr.length > 0) { + data.tracks.forEach(function(track) { + if (track.name === fr[0][1]) { + found = track; + } + }); + } + } + + if (!found) { + if (data.tracks.length > 0) { + found = data.tracks[0]; + } + } + + console.log('found', found); + if (found) { + g_tracks.push(found.uri); + txt += '
' + + '' + + '' + + '
\n'; + } else { + txt += '
No match found for the word "' + data.word+ '"
\n' + } + }); + + $('#debug').html(txt); + }); + } + + var doSearch = function(word, callback) { + console.log('search for ' + word); + var url = 'https://api.spotify.com/v1/search?type=track&limit=50&q=' + encodeURIComponent('track:"'+word+'"'); + $.ajax(url, { + dataType: 'json', + success: function(r) { + console.log('got track', r); + callback({ + word: word, + tracks: r.tracks.items + .map(function(item) { + var ret = { + name: item.name, + artist: 'Unknown', + artist_uri: '', + album: item.album.name, + album_uri: item.album.uri, + cover_url: '', + uri: item.uri + } + if (item.artists.length > 0) { + ret.artist = item.artists[0].name; + ret.artist_uri = item.artists[0].uri; + } + if (item.album.images.length > 0) { + ret.cover_url = item.album.images[item.album.images.length - 1].url; + } + return ret; + }) + }); + }, + error: function(r) { + callback({ + word: word, + tracks: [] + }); + } + }); + } + + var resolveOneWord = function() { + var word = cache.pop(); + if (word) { + console.log('time to resolve word', word); + setStatus('Looking up the word "' + word + '"'); + doSearch(word, function(result) { + console.log('got word result', result); + cache.store(word, result); + setTimeout(resolveOneWord, 1); + }); + } else { + console.log('no words to look up...'); + setTimeout(resolveOneWord, 1000); + } + } + + var g_access_token = ''; + var g_username = ''; + + var client_id = ''; + var redirect_uri = ''; + + + if (location.host == 'localhost:8000') { + client_id = 'd37a9e88667b4fb3bc994299de2a52bd'; + redirect_uri = 'http://localhost:8000/callback.html'; + } else { + client_id = '6f9391eff32647baa44b1a700ad4a7fc'; + redirect_uri = 'http://lab.possan.se/playlistcreator-example/callback.html'; + } + + var doLogin = function(callback) { + var url = 'https://accounts.spotify.com/authorize?client_id=' + client_id + + '&response_type=token' + + '&scope=playlist-read-private%20playlist-modify%20playlist-modify-private' + + '&redirect_uri=' + encodeURIComponent(redirect_uri); + localStorage.setItem('createplaylist-tracks', JSON.stringify(g_tracks)); + localStorage.setItem('createplaylist-name', g_name); + var w = window.open(url, 'asdf', 'WIDTH=400,HEIGHT=500'); + } + + var refreshtimer = 0; + var queueRefreshText = function() { + if (refreshtimer) { + clearTimeout(refreshtimer); + } + refreshtimer = setTimeout(function() { + refreshText(); + }, 1000); + } + + exports.startApp = function() { + setStatus(''); + console.log('start app.'); + $('#alltext').keyup(function() { + queueRefreshText(); + }); + $('#alltext').change(function() { + queueRefreshText(); + }); + $('#start').click(function() { + doLogin(function() {}); + }) + $('#alltext').text('hello world'); + refreshText(); + resolveOneWord(); +} + +})(window); diff --git a/assets/js/fuzzyset.js b/assets/js/fuzzyset.js new file mode 100644 index 0000000..1ab373f --- /dev/null +++ b/assets/js/fuzzyset.js @@ -0,0 +1,275 @@ +// from https://github.com/Glench/fuzzyset.js + +var FuzzySet = function(arr, useLevenshtein, gramSizeLower, gramSizeUpper) { + var fuzzyset = { + version: '0.0.1' + }; + + // default options + arr = arr || []; + fuzzyset.gramSizeLower = gramSizeLower || 2; + fuzzyset.gramSizeUpper = gramSizeUpper || 3; + fuzzyset.useLevenshtein = useLevenshtein || true; + + // define all the object functions and attributes + fuzzyset.exactSet = {} + fuzzyset.matchDict = {}; + fuzzyset.items = {}; + + // helper functions + var levenshtein = function(str1, str2) { + var current = [], prev, value; + + for (var i = 0; i <= str2.length; i++) + for (var j = 0; j <= str1.length; j++) { + if (i && j) + if (str1.charAt(j - 1) === str2.charAt(i - 1)) + value = prev; + else + value = Math.min(current[j], current[j - 1], prev) + 1; + else + value = i + j; + + prev = current[j]; + current[j] = value; + } + + return current.pop(); + }; + + // return an edit distance from 0 to 1 + var _distance = function(str1, str2) { + if (str1 == null && str2 == null) throw 'Trying to compare two null values' + if (str1 == null || str2 == null) return 0; + str1 = String(str1); str2 = String(str2); + + var distance = levenshtein(str1, str2); + if (str1.length > str2.length) { + return 1 - distance / str1.length; + } else { + return 1 - distance / str2.length; + } + }; + var _nonWordRe = /[^\w, ]+/; + + var _iterateGrams = function(value, gramSize) { + gramSize = gramSize || 2; + var simplified = '-' + value.toLowerCase().replace(_nonWordRe, '') + '-', + lenDiff = gramSize - simplified.length, + results = []; + if (lenDiff > 0) { + for (var i = 0; i < lenDiff; ++i) { + value += '-'; + } + } + for (var i = 0; i < simplified.length - gramSize + 1; ++i) { + results.push(simplified.slice(i, i + gramSize)) + } + return results; + }; + + var _gramCounter = function(value, gramSize) { + gramSize = gramSize || 2; + var result = {}, + grams = _iterateGrams(value, gramSize), + i = 0; + for (i; i < grams.length; ++i) { + if (grams[i] in result) { + result[grams[i]] += 1; + } else { + result[grams[i]] = 1; + } + } + return result; + }; + + // the main functions + fuzzyset.get = function(value, defaultValue) { + var result = this._get(value); + if (!result && defaultValue) { + return defaultValue; + } + return result; + }; + + fuzzyset._get = function(value) { + var normalizedValue = this._normalizeStr(value), + result = this.exactSet[normalizedValue]; + if (result) { + return [[1, result]]; + } + var results = []; + for (var gramSize = this.gramSizeUpper; gramSize > this.gramSizeLower; --gramSize) { + results = this.__get(value, gramSize); + if (results) { + return results; + } + } + return null; + }; + + fuzzyset.__get = function(value, gramSize) { + var normalizedValue = this._normalizeStr(value), + matches = {}, + gramCounts = _gramCounter(normalizedValue, gramSize), + items = this.items[gramSize], + sumOfSquareGramCounts = 0, + gram, + gramCount, + i, + index, + otherGramCount; + + for (gram in gramCounts) { + gramCount = gramCounts[gram]; + sumOfSquareGramCounts += Math.pow(gramCount, 2); + if (gram in this.matchDict) { + for (i = 0; i < this.matchDict[gram].length; ++i) { + index = this.matchDict[gram][i][0]; + otherGramCount = this.matchDict[gram][i][1]; + if (index in matches) { + matches[index] += gramCount * otherGramCount; + } else { + matches[index] = gramCount * otherGramCount; + } + } + } + } + + function isEmptyObject(obj) { + for(var prop in obj) { + if(obj.hasOwnProperty(prop)) + return false; + } + return true; + } + + if (isEmptyObject(matches)) { + return null; + } + + var vectorNormal = Math.sqrt(sumOfSquareGramCounts), + results = [], + matchScore; + // build a results list of [score, str] + for (var matchIndex in matches) { + matchScore = matches[matchIndex]; + results.push([matchScore / (vectorNormal * items[matchIndex][0]), items[matchIndex][1]]); + } + var sortDescending = function(a, b) { + if (a[0] < b[0]) { + return 1; + } else if (a[0] > b[0]) { + return -1; + } else { + return 0; + } + }; + results.sort(sortDescending); + if (this.useLevenshtein) { + var newResults = [], + endIndex = Math.min(50, results.length); + // truncate somewhat arbitrarily to 50 + for (var i = 0; i < endIndex; ++i) { + newResults.push([_distance(results[i][1], normalizedValue), results[i][1]]); + } + results = newResults; + results.sort(sortDescending); + } + var newResults = []; + for (var i = 0; i < results.length; ++i) { + if (results[i][0] == results[0][0]) { + newResults.push([results[i][0], this.exactSet[results[i][1]]]); + } + } + return newResults; + }; + + fuzzyset.add = function(value) { + var normalizedValue = this._normalizeStr(value); + if (normalizedValue in this.exactSet) { + return false; + } + + var i = this.gramSizeLower; + for (i; i < this.gramSizeUpper + 1; ++i) { + this._add(value, i); + } + }; + + fuzzyset._add = function(value, gramSize) { + var normalizedValue = this._normalizeStr(value), + items = this.items[gramSize] || [], + index = items.length; + + items.push(0); + var gramCounts = _gramCounter(normalizedValue, gramSize), + sumOfSquareGramCounts = 0, + gram, gramCount; + for (var gram in gramCounts) { + gramCount = gramCounts[gram]; + sumOfSquareGramCounts += Math.pow(gramCount, 2); + if (gram in this.matchDict) { + this.matchDict[gram].push([index, gramCount]); + } else { + this.matchDict[gram] = [[index, gramCount]]; + } + } + var vectorNormal = Math.sqrt(sumOfSquareGramCounts); + items[index] = [vectorNormal, normalizedValue]; + this.items[gramSize] = items; + this.exactSet[normalizedValue] = value; + }; + + fuzzyset._normalizeStr = function(str) { + if (Object.prototype.toString.call(str) !== '[object String]') throw 'Must use a string as argument to FuzzySet functions' + return str.toLowerCase(); + }; + + // return length of items in set + fuzzyset.length = function() { + var count = 0, + prop; + for (prop in this.exactSet) { + if (this.exactSet.hasOwnProperty(prop)) { + count += 1; + } + } + return count; + }; + + // return is set is empty + fuzzyset.isEmpty = function() { + for (var prop in this.exactSet) { + if (this.exactSet.hasOwnProperty(prop)) { + return false; + } + } + return true; + }; + + // return list of values loaded into set + fuzzyset.values = function() { + var values = [], + prop; + for (prop in this.exactSet) { + if (this.exactSet.hasOwnProperty(prop)) { + values.push(this.exactSet[prop]) + } + } + return values; + }; + + + // initialization + var i = fuzzyset.gramSizeLower; + for (i; i < fuzzyset.gramSizeUpper + 1; ++i) { + fuzzyset.items[i] = []; + } + // add all the items to the set + for (i = 0; i < arr.length; ++i) { + fuzzyset.add(arr[i]); + } + + return fuzzyset; +}; \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..2231f37 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,112 @@ +var g_access_token = ""; + var g_username = ""; + var g_tracks = []; + + function getUsername(callback) { + console.log("getUsername"); + var url = "https://api.spotify.com/v1/me"; + $.ajax(url, { + dataType: "json", + headers: { + Authorization: "Bearer " + g_access_token + }, + success: function (r) { + console.log("got username response", r); + callback(r.id); + }, + error: function (r) { + callback(null); + } + }); + } + + function createPlaylist(username, name, callback) { + console.log("createPlaylist", username, name); + var url = "https://api.spotify.com/v1/users/" + username + "/playlists"; + $.ajax(url, { + method: "POST", + data: JSON.stringify({ + name: name, + public: false + }), + dataType: "json", + headers: { + Authorization: "Bearer " + g_access_token, + "Content-Type": "application/json" + }, + success: function (r) { + console.log("create playlist response", r); + callback(r.id); + }, + error: function (r) { + callback(null); + } + }); + } + + function addTracksToPlaylist(username, playlist, tracks, callback) { + console.log("addTracksToPlaylist", username, playlist, tracks); + var url = + "https://api.spotify.com/v1/users/" + + username + + "/playlists/" + + playlist + + "/tracks"; // ?uris='+encodeURIComponent(tracks.join(',')); + $.ajax(url, { + method: "POST", + data: JSON.stringify(tracks), + dataType: "text", + headers: { + Authorization: "Bearer " + g_access_token, + "Content-Type": "application/json" + }, + success: function (r) { + console.log("add track response", r); + callback(r.id); + }, + error: function (r) { + callback(null); + } + }); + } + + function doit() { + // parse hash + var hash = location.hash.replace(/#/g, ""); + var all = hash.split("&"); + var args = {}; + console.log("all", all); + all.forEach(function (keyvalue) { + var idx = keyvalue.indexOf("="); + var key = keyvalue.substring(0, idx); + var val = keyvalue.substring(idx + 1); + args[key] = val; + }); + + g_name = localStorage.getItem("createplaylist-name"); + g_tracks = JSON.parse(localStorage.getItem("createplaylist-tracks")); + + console.log("got args", args); + + if (typeof args["access_token"] != "undefined") { + // got access token + console.log("got access token", args["access_token"]); + g_access_token = args["access_token"]; + } + + getUsername(function (username) { + console.log("got username", username); + createPlaylist(username, g_name, function (playlist) { + console.log("created playlist", playlist); + addTracksToPlaylist(username, playlist, g_tracks, function () { + console.log("tracks added."); + $("#playlistlink").attr( + "href", + "spotify:user:" + username + ":playlist:" + playlist + ); + $("#creating").hide(); + $("#done").show(); + }); + }); + }); + } \ No newline at end of file diff --git a/assets/js/wordcache.js b/assets/js/wordcache.js new file mode 100644 index 0000000..db7bc6f --- /dev/null +++ b/assets/js/wordcache.js @@ -0,0 +1,89 @@ +(function(exports) { + +var WordCache = function() { + this.cachedwords = []; + this.queue = []; + this.data = JSON.parse(localStorage.getItem('wordcache') || '{}'); + this.listeners = []; +} + +WordCache.prototype.pop = function() { + if (this.queue.length == 0) { + return null; + } + + var item = this.queue[0]; + this.queue.splice(0, 1); + return item; +} + +WordCache.prototype.saveCache = function() { + localStorage.setItem('wordcache', JSON.stringify(this.data)); +} + +WordCache.prototype.store = function(word, data) { + console.log('store', word, data); + if (this.cachedwords.indexOf(word) == -1) { + this.cachedwords.push(word); + } + var idx = this.queue.indexOf(word); + if (idx != -1) { + this.queue.splice(idx, 1); + } + this.data[word] = data; + this.callFulfilledListeners(); + this.saveCache(); +} + +WordCache.prototype.callFulfilledListeners = function() { + console.log('callFulfilledListeners', this.listeners); + // callback(words); + var _this = this; + this.listeners.forEach(function(item) { + console.log('check listener', item); + + var anymissing = false; + var result = []; + for(var i=0; i +
+
+

Creating playlist...

+
+
+
+ +
+ \ No newline at end of file diff --git a/index.md b/index.md new file mode 100644 index 0000000..309b8bc --- /dev/null +++ b/index.md @@ -0,0 +1,19 @@ +--- +layout: default +--- + +
+
+

Write your text here

+
+ +
+
+ +
+
+
+

Corresponding track names

+
+
+
\ No newline at end of file From ff3bd9cced7a8366978bd2087b166d07156a3206 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 17:58:22 -0300 Subject: [PATCH 03/33] Fix Assets --- assets/css/main.css | 1 + index.md | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index 6b2feb4..3f7655e 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -1,6 +1,7 @@ .column left { position: absolute; width: 50%; + height: 100%; } .column right { diff --git a/index.md b/index.md index 309b8bc..9ac1fbb 100644 --- a/index.md +++ b/index.md @@ -3,17 +3,17 @@ layout: default ---
-
-

Write your text here

-
+
+

Write your text here

+
-
-
- -
-
-

Corresponding track names

-
-
-
\ No newline at end of file +
+ +
+
+
+

Corresponding track names

+
+
+
\ No newline at end of file From 6e86b89c90a3824714aa072315e33c8759a14f85 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:00:40 -0300 Subject: [PATCH 04/33] Set docs --- README.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 68663d5..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This is an example application that creates a playlist using the [Spotify Web API](https://developer.spotify.com/web-api/). - -Please check out the **[demo](http://lab.possan.se/playlistcreator-example/)** to try it out! From 3580c243ad86a0128a6f107f0dbf4dfd0a2b56f1 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:01:05 -0300 Subject: [PATCH 05/33] Set Docs --- docs/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..68663d5 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +This is an example application that creates a playlist using the [Spotify Web API](https://developer.spotify.com/web-api/). + +Please check out the **[demo](http://lab.possan.se/playlistcreator-example/)** to try it out! From 14ea2dd6b614b869e48525d53e298b43a1c2273d Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:18:05 -0300 Subject: [PATCH 06/33] Set app to Bulma --- assets/js/app.js | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index 4bf6348..24af0f2 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -5,18 +5,10 @@ var g_name = ''; var g_tracks = ''; - function setStatus(text) { - if (text != '') { - $('#status').html( - '
' + - '
' + - text + - '
' + - '
' - ); - } else { - $('#status').html(''); - } + function setStatus() { + $('#status').html( + '15%' + ); } var Playlist = function() { @@ -32,14 +24,13 @@ } var refreshText = function() { - setStatus('Updating text...'); + setStatus(); g_name = $('#alltext').val().trim(); var words = splitText(g_name); console.log('text changed.', g_name, words); cache.lookupWords(words, function(worddata) { - - setStatus(''); + setStatus(); console.log('wordcache callback', worddata); // $('#debug').text(JSON.stringify(worddata, null, 2)); @@ -93,13 +84,18 @@ console.log('found', found); if (found) { g_tracks.push(found.uri); - txt += '
' + - '' + - '' + + txt += + '
' + + '' + + '' + + '' + + '
' + + '

'+ + '' + found.name + ''+ + '

' + + 'Album: ' + found.album +''+ + '
Artist: ' + found.artist+'' + + '
' + '
\n'; } else { txt += '
No match found for the word "' + data.word+ '"
\n' From 7758ca34e40057f8d510fe0b641d3f641b07a38b Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:20:30 -0300 Subject: [PATCH 07/33] Update Index --- assets/js/app.js | 2 +- index.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index 24af0f2..65cd7f5 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -7,7 +7,7 @@ function setStatus() { $('#status').html( - '15%' + '15%' ); } diff --git a/index.md b/index.md index 9ac1fbb..0ccf387 100644 --- a/index.md +++ b/index.md @@ -10,10 +10,11 @@ layout: default

-

Corresponding track names

+
+
\ No newline at end of file From 1469ffee20ab09532a21140bdb10360ccc5dacae Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:24:38 -0300 Subject: [PATCH 08/33] Update Status --- assets/js/app.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index 65cd7f5..969d48e 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -5,10 +5,14 @@ var g_name = ''; var g_tracks = ''; - function setStatus() { - $('#status').html( - '15%' - ); + function setStatus(text) { + if(text != null){ + $('#status').html( + '15%' + ); + } else { + $('#status').html(); + } } var Playlist = function() { @@ -24,13 +28,13 @@ } var refreshText = function() { - setStatus(); + setStatus(''); g_name = $('#alltext').val().trim(); var words = splitText(g_name); console.log('text changed.', g_name, words); cache.lookupWords(words, function(worddata) { - setStatus(); + setStatus(''); console.log('wordcache callback', worddata); // $('#debug').text(JSON.stringify(worddata, null, 2)); From 776866c27b893d084f88d91707f32cb99dce8b48 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:26:03 -0300 Subject: [PATCH 09/33] Update Index --- index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.md b/index.md index 0ccf387..323c341 100644 --- a/index.md +++ b/index.md @@ -6,7 +6,7 @@ layout: default

Write your text here

- +

From 38abad3b305b3b982e375d321fa7052d7e943bfe Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:27:53 -0300 Subject: [PATCH 10/33] Update Status --- assets/js/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index 969d48e..24484ca 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -6,12 +6,12 @@ var g_tracks = ''; function setStatus(text) { - if(text != null){ + if(text != ''){ $('#status').html( '15%' ); } else { - $('#status').html(); + $('#status').html(''); } } @@ -28,7 +28,7 @@ } var refreshText = function() { - setStatus(''); + setStatus('Loading...'); g_name = $('#alltext').val().trim(); var words = splitText(g_name); From 0e591a32698a4321d1dd3642bcfe87005e893bea Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:32:20 -0300 Subject: [PATCH 11/33] Change meta --- _layouts/default.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_layouts/default.html b/_layouts/default.html index db5bc0e..33f2cd1 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -6,7 +6,7 @@ - {{page.title}} + {{site.title}} From c775d77aab965c0b8d7c0556d0a061876dfffecf Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:37:23 -0300 Subject: [PATCH 12/33] Set Default --- _layouts/default.html | 35 +++++++++++++++++++++-------------- assets/css/logo_urb.png | Bin 0 -> 17231 bytes 2 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 assets/css/logo_urb.png diff --git a/_layouts/default.html b/_layouts/default.html index 33f2cd1..44c4ebb 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -1,15 +1,24 @@ - - - - - - + + + + {{site.title}} - + + + + + + - + + + +
{{content}}
- - - - - - + + + + \ No newline at end of file diff --git a/assets/css/logo_urb.png b/assets/css/logo_urb.png new file mode 100644 index 0000000000000000000000000000000000000000..f89c75e4af0d9829cf656805d8ef2191ad771522 GIT binary patch literal 17231 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 Date: Tue, 22 Feb 2022 18:39:12 -0300 Subject: [PATCH 13/33] Create SECURITY.md --- SECURITY.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..c57a29b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Firefox | Safari | Edge | Opera | +| ------- | ------------------ |------------------ |------------------ |------------------ | +| 2022.2.x | :x: | :x: |:x: |:x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. From 11914c0cc96d36667089215fe0c218a359efef63 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:39:25 -0300 Subject: [PATCH 14/33] Rename SECURITY.md to docs/SECURITY.md --- SECURITY.md => docs/SECURITY.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename SECURITY.md => docs/SECURITY.md (100%) diff --git a/SECURITY.md b/docs/SECURITY.md similarity index 100% rename from SECURITY.md rename to docs/SECURITY.md From 2a31e650b47674248e5513dcd31fcea2b7ee4766 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:40:58 -0300 Subject: [PATCH 15/33] Update README.md --- docs/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/README.md b/docs/README.md index 68663d5..0582f4a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,11 @@ +# Playlist Creator for Spotify + + +[![GitHub license](https://img.shields.io/github/license/clcmo/playlistcreator-example?style=for-the-badge)](https://github.com/clcmo/playlistcreator-example) +[![GitHub stars](https://img.shields.io/github/stars/clcmo/playlistcreator-example?style=for-the-badge)](https://github.com/clcmo/playlistcreator-example/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/clcmo/playlistcreator-example?style=for-the-badge)](https://github.com/clcmo/playlistcreator-example/network) +[![GitHub issues](https://img.shields.io/github/issues/clcmo/playlistcreator-example?style=for-the-badge)](https://github.com/clcmo/playlistcreator-example/issues) + This is an example application that creates a playlist using the [Spotify Web API](https://developer.spotify.com/web-api/). Please check out the **[demo](http://lab.possan.se/playlistcreator-example/)** to try it out! From 002b6f2a37ace8133541f14fc41f5c95ebca96cd Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:41:39 -0300 Subject: [PATCH 16/33] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..613cfa2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Camila L. Oliveira + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 7c3288b244c723cd9ab4a3bb334fa8152f2040a7 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:44:35 -0300 Subject: [PATCH 17/33] Fix Css --- _layouts/default.html | 2 +- assets/css/{main.css => default.css} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename assets/css/{main.css => default.css} (100%) diff --git a/_layouts/default.html b/_layouts/default.html index 44c4ebb..d48ca9b 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -10,7 +10,7 @@ - + diff --git a/assets/css/main.css b/assets/css/default.css similarity index 100% rename from assets/css/main.css rename to assets/css/default.css From 42207527b7078c0a4b15a6f6a1081f3d0286a071 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:48:33 -0300 Subject: [PATCH 18/33] Config baseurl --- _config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_config.yml b/_config.yml index 2faec3b..b679ed5 100644 --- a/_config.yml +++ b/_config.yml @@ -1,7 +1,7 @@ name: Playlist Generator title: Playlist Generator description: Create your Spotify Playlist -baseurl: +baseurl: http://clcmo.github.io/playlistcreator-example/ include: ["_pages"] permalink: /:title/ From bd850df6065f24f3de67ef07f55a819021a2bf5a Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:50:17 -0300 Subject: [PATCH 19/33] Set BaseUrl --- _config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_config.yml b/_config.yml index b679ed5..5e9cc0a 100644 --- a/_config.yml +++ b/_config.yml @@ -1,7 +1,7 @@ name: Playlist Generator title: Playlist Generator description: Create your Spotify Playlist -baseurl: http://clcmo.github.io/playlistcreator-example/ +baseurl: http://lab.portalurbanna.com.br/ include: ["_pages"] permalink: /:title/ From b65f3cd37a22f1c771a6c0c7ef11150b78964823 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 18:50:40 -0300 Subject: [PATCH 20/33] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..8700f8f --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +lab.portalurbanna.com.br \ No newline at end of file From 933cc1c58711c6e4aec696a1f592c0138319f544 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 19:01:01 -0300 Subject: [PATCH 21/33] Delete CNAME --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index 8700f8f..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -lab.portalurbanna.com.br \ No newline at end of file From c5891e007e9477d180753439fcf2434956b67d65 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 19:02:05 -0300 Subject: [PATCH 22/33] Update CNAME --- CNAME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CNAME b/CNAME index 8700f8f..054884e 100644 --- a/CNAME +++ b/CNAME @@ -1 +1 @@ -lab.portalurbanna.com.br \ No newline at end of file +dev.portalurbanna.com.br \ No newline at end of file From 140c05b507dfa09c0f08e50d56f2c66314aa3773 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 19:04:37 -0300 Subject: [PATCH 23/33] Update README.md --- docs/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0582f4a..b171946 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,10 @@ # Playlist Creator for Spotify -[![GitHub license](https://img.shields.io/github/license/clcmo/playlistcreator-example?style=for-the-badge)](https://github.com/clcmo/playlistcreator-example) -[![GitHub stars](https://img.shields.io/github/stars/clcmo/playlistcreator-example?style=for-the-badge)](https://github.com/clcmo/playlistcreator-example/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/clcmo/playlistcreator-example?style=for-the-badge)](https://github.com/clcmo/playlistcreator-example/network) -[![GitHub issues](https://img.shields.io/github/issues/clcmo/playlistcreator-example?style=for-the-badge)](https://github.com/clcmo/playlistcreator-example/issues) +[![GitHub license](https://img.shields.io/github/license/urbanna/playlistcreator-example?style=for-the-badge)](https://github.com/urbanna/playlistcreator-example) +[![GitHub stars](https://img.shields.io/github/stars/urbanna/playlistcreator-example?style=for-the-badge)](https://github.com/urbanna/playlistcreator-example/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/urbanna/playlistcreator-example?style=for-the-badge)](https://github.com/urbanna/playlistcreator-example/network) +[![GitHub issues](https://img.shields.io/github/issues/urbanna/playlistcreator-example?style=for-the-badge)](https://github.com/urbanna/playlistcreator-example/issues) This is an example application that creates a playlist using the [Spotify Web API](https://developer.spotify.com/web-api/). From 1a9208191335d80d9810cdd461da615df3b94cd4 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 19:10:16 -0300 Subject: [PATCH 24/33] Update Link on Config --- _config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_config.yml b/_config.yml index 5e9cc0a..bc2739b 100644 --- a/_config.yml +++ b/_config.yml @@ -1,7 +1,7 @@ name: Playlist Generator title: Playlist Generator description: Create your Spotify Playlist -baseurl: http://lab.portalurbanna.com.br/ +baseurl: http://dev.portalurbanna.com.br/ include: ["_pages"] permalink: /:title/ From 3bc8a3ddeb5fb596de33affeac2e466af6071094 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 19:58:36 -0300 Subject: [PATCH 25/33] Update README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index b171946..bd963a7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,4 +8,4 @@ This is an example application that creates a playlist using the [Spotify Web API](https://developer.spotify.com/web-api/). -Please check out the **[demo](http://lab.possan.se/playlistcreator-example/)** to try it out! +Please check out the **[demo](http://dev.portalurbanna.com.br/)** to try it out! From aaf2b9a31f9517cfc8aa9c6033f7e97da2f96de7 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 20:32:19 -0300 Subject: [PATCH 26/33] Create Login --- _layouts/default.html | 1 + assets/js/app.js | 367 +++++++++++++++++------------------------- assets/js/login.js | 75 +++++++++ assets/js/new-app.js | 221 +++++++++++++++++++++++++ assets/js/server.js | 10 ++ login.md | 43 +++++ package-lock.json | 6 + 7 files changed, 503 insertions(+), 220 deletions(-) create mode 100644 assets/js/login.js create mode 100644 assets/js/new-app.js create mode 100644 assets/js/server.js create mode 100644 login.md create mode 100644 package-lock.json diff --git a/_layouts/default.html b/_layouts/default.html index d48ca9b..5bb7339 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -49,4 +49,5 @@

+ \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js index 24484ca..d5aa918 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,221 +1,148 @@ -(function(exports) { - var cache = new WordCache(); - - var g_name = ''; - var g_tracks = ''; - - function setStatus(text) { - if(text != ''){ - $('#status').html( - '15%' - ); - } else { - $('#status').html(''); - } - } - - var Playlist = function() { - } - - var splitText = function(inputtext) { - var words = inputtext - .split(/[ \n\r\t]/) - .map(function(w) { return w.toLowerCase().trim().replace(/^[.,-]+/,'').replace(/[.,-]+$/g,''); }) - .filter(function(w) { return (w.length > 0); }); - words = words.slice(0, 100); - return words; - } - - var refreshText = function() { - setStatus('Loading...'); - - g_name = $('#alltext').val().trim(); - var words = splitText(g_name); - console.log('text changed.', g_name, words); - cache.lookupWords(words, function(worddata) { - setStatus(''); - - console.log('wordcache callback', worddata); - // $('#debug').text(JSON.stringify(worddata, null, 2)); - - var txt = ''; - g_tracks = []; - worddata.forEach(function(data) { - console.log('word', data.word); - - data.tracks.sort(function(a,b) { - return Math.random() - 0.5; - }) - - var names = data.tracks.map(function(track) { - return track.name; - }); - - console.log('names', names); - - var f = new FuzzySet(names); - var fr = f.get(data.word); - console.log('fr', fr); - - var found = null; - var title = ''; - - if (!found) { - data.tracks.forEach(function(track) { - if (track.name.toLowerCase().trim() === data.word.toLowerCase().trim()) { - found = track; - } - }); - } - - if (!found) { - if (fr && fr.length > 0) { - data.tracks.forEach(function(track) { - if (track.name === fr[0][1]) { - found = track; - } - }); - } - } - - if (!found) { - if (data.tracks.length > 0) { - found = data.tracks[0]; - } - } - - console.log('found', found); - if (found) { - g_tracks.push(found.uri); - txt += - '
' + - '' + - '' + - '' + - '
' + - '

'+ - '' + found.name + ''+ - '

' + - 'Album: ' + found.album +''+ - '
Artist: ' + found.artist+'' + - '
' + - '
\n'; - } else { - txt += '
No match found for the word "' + data.word+ '"
\n' - } - }); - - $('#debug').html(txt); - }); - } - - var doSearch = function(word, callback) { - console.log('search for ' + word); - var url = 'https://api.spotify.com/v1/search?type=track&limit=50&q=' + encodeURIComponent('track:"'+word+'"'); - $.ajax(url, { - dataType: 'json', - success: function(r) { - console.log('got track', r); - callback({ - word: word, - tracks: r.tracks.items - .map(function(item) { - var ret = { - name: item.name, - artist: 'Unknown', - artist_uri: '', - album: item.album.name, - album_uri: item.album.uri, - cover_url: '', - uri: item.uri - } - if (item.artists.length > 0) { - ret.artist = item.artists[0].name; - ret.artist_uri = item.artists[0].uri; - } - if (item.album.images.length > 0) { - ret.cover_url = item.album.images[item.album.images.length - 1].url; - } - return ret; - }) - }); - }, - error: function(r) { - callback({ - word: word, - tracks: [] - }); - } - }); - } - - var resolveOneWord = function() { - var word = cache.pop(); - if (word) { - console.log('time to resolve word', word); - setStatus('Looking up the word "' + word + '"'); - doSearch(word, function(result) { - console.log('got word result', result); - cache.store(word, result); - setTimeout(resolveOneWord, 1); - }); - } else { - console.log('no words to look up...'); - setTimeout(resolveOneWord, 1000); - } - } - - var g_access_token = ''; - var g_username = ''; - - var client_id = ''; - var redirect_uri = ''; - - - if (location.host == 'localhost:8000') { - client_id = 'd37a9e88667b4fb3bc994299de2a52bd'; - redirect_uri = 'http://localhost:8000/callback.html'; - } else { - client_id = '6f9391eff32647baa44b1a700ad4a7fc'; - redirect_uri = 'http://lab.possan.se/playlistcreator-example/callback.html'; - } - - var doLogin = function(callback) { - var url = 'https://accounts.spotify.com/authorize?client_id=' + client_id + - '&response_type=token' + - '&scope=playlist-read-private%20playlist-modify%20playlist-modify-private' + - '&redirect_uri=' + encodeURIComponent(redirect_uri); - localStorage.setItem('createplaylist-tracks', JSON.stringify(g_tracks)); - localStorage.setItem('createplaylist-name', g_name); - var w = window.open(url, 'asdf', 'WIDTH=400,HEIGHT=500'); - } - - var refreshtimer = 0; - var queueRefreshText = function() { - if (refreshtimer) { - clearTimeout(refreshtimer); - } - refreshtimer = setTimeout(function() { - refreshText(); - }, 1000); - } - - exports.startApp = function() { - setStatus(''); - console.log('start app.'); - $('#alltext').keyup(function() { - queueRefreshText(); - }); - $('#alltext').change(function() { - queueRefreshText(); - }); - $('#start').click(function() { - doLogin(function() {}); - }) - $('#alltext').text('hello world'); - refreshText(); - resolveOneWord(); -} - -})(window); +/** + * This is an example of a basic node.js script that performs + * the Authorization Code oAuth2 flow to authenticate against + * the Spotify Accounts. + * + * For more information, read + * https://developer.spotify.com/web-api/authorization-guide/#authorization_code_flow + */ + + var express = require('express'); // Express web server framework + var request = require('request'); // "Request" library + var cors = require('cors'); + var querystring = require('querystring'); + var cookieParser = require('cookie-parser'); + + var client_id = '2a8909c2980143c496833de968725bf0'; // Your client id + var client_secret = '90cbdda3e056407bbd8051d711f6047a'; // Your secret + var redirect_uri = 'https://dev.portalurbanna.com.br/callback'; // Your redirect uri + + /** + * Generates a random string containing numbers and letters + * @param {number} length The length of the string + * @return {string} The generated string + */ + var generateRandomString = function(length) { + var text = ''; + var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + for (var i = 0; i < length; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; + }; + + var stateKey = 'spotify_auth_state'; + + var app = express(); + + app.use(express.static(__dirname + '/public')) + .use(cors()) + .use(cookieParser()); + + app.get('/login', function(req, res) { + + var state = generateRandomString(16); + res.cookie(stateKey, state); + + // your application requests authorization + var scope = 'user-read-private user-read-email'; + res.redirect('https://accounts.spotify.com/authorize?' + + querystring.stringify({ + response_type: 'code', + client_id: client_id, + scope: scope, + redirect_uri: redirect_uri, + state: state + })); + }); + + app.get('/callback', function(req, res) { + + // your application requests refresh and access tokens + // after checking the state parameter + + var code = req.query.code || null; + var state = req.query.state || null; + var storedState = req.cookies ? req.cookies[stateKey] : null; + + if (state === null || state !== storedState) { + res.redirect('/#' + + querystring.stringify({ + error: 'state_mismatch' + })); + } else { + res.clearCookie(stateKey); + var authOptions = { + url: 'https://accounts.spotify.com/api/token', + form: { + code: code, + redirect_uri: redirect_uri, + grant_type: 'authorization_code' + }, + headers: { + 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) + }, + json: true + }; + + request.post(authOptions, function(error, response, body) { + if (!error && response.statusCode === 200) { + + var access_token = body.access_token, + refresh_token = body.refresh_token; + + var options = { + url: 'https://api.spotify.com/v1/me', + headers: { 'Authorization': 'Bearer ' + access_token }, + json: true + }; + + // use the access token to access the Spotify Web API + request.get(options, function(error, response, body) { + console.log(body); + }); + + // we can also pass the token to the browser to make requests from there + res.redirect('/#' + + querystring.stringify({ + access_token: access_token, + refresh_token: refresh_token + })); + } else { + res.redirect('/#' + + querystring.stringify({ + error: 'invalid_token' + })); + } + }); + } + }); + + app.get('/refresh_token', function(req, res) { + + // requesting access token from refresh token + var refresh_token = req.query.refresh_token; + var authOptions = { + url: 'https://accounts.spotify.com/api/token', + headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) }, + form: { + grant_type: 'refresh_token', + refresh_token: refresh_token + }, + json: true + }; + + request.post(authOptions, function(error, response, body) { + if (!error && response.statusCode === 200) { + var access_token = body.access_token; + res.send({ + 'access_token': access_token + }); + } + }); + }); + + console.log('Listening on 8888'); + app.listen(8888); \ No newline at end of file diff --git a/assets/js/login.js b/assets/js/login.js new file mode 100644 index 0000000..76044ad --- /dev/null +++ b/assets/js/login.js @@ -0,0 +1,75 @@ +(function() { + + /** + * Obtains parameters from the hash of the URL + * @return Object + */ + + function getHashParams() { + var hashParams = {}; + var e, r = /([^&;=]+)=?([^&;]*)/g, + q = window.location.hash.substring(1); + while ( e = r.exec(q)) { + hashParams[e[1]] = decodeURIComponent(e[2]); + } + return hashParams; + } + + var userProfileSource = document.getElementById('user-profile-template').innerHTML, + userProfileTemplate = Handlebars.compile(userProfileSource), + userProfilePlaceholder = document.getElementById('user-profile'); + + var oauthSource = document.getElementById('oauth-template').innerHTML, + oauthTemplate = Handlebars.compile(oauthSource), + oauthPlaceholder = document.getElementById('oauth'); + + var params = getHashParams(); + + var access_token = params.access_token, + refresh_token = params.refresh_token, + error = params.error; + + if (error) { + alert('There was an error during the authentication'); + } else { + if (access_token) { + // render oauth info + oauthPlaceholder.innerHTML = oauthTemplate({ + access_token: access_token, + refresh_token: refresh_token + }); + + $.ajax({ + url: 'https://api.spotify.com/v1/me', + headers: { + 'Authorization': 'Bearer ' + access_token + }, + success: function(response) { + userProfilePlaceholder.innerHTML = userProfileTemplate(response); + + $('#login').hide(); + $('#loggedin').show(); + } + }); + } else { + // render initial screen + $('#login').show(); + $('#loggedin').hide(); + } + + document.getElementById('obtain-new-token').addEventListener('click', function() { + $.ajax({ + url: '/refresh_token', + data: { + 'refresh_token': refresh_token + } + }).done(function(data) { + access_token = data.access_token; + oauthPlaceholder.innerHTML = oauthTemplate({ + access_token: access_token, + refresh_token: refresh_token + }); + }); + }, false); + } + })(); \ No newline at end of file diff --git a/assets/js/new-app.js b/assets/js/new-app.js new file mode 100644 index 0000000..05b58c8 --- /dev/null +++ b/assets/js/new-app.js @@ -0,0 +1,221 @@ +(function(exports) { + + var cache = new WordCache(); + + var g_name = ''; + var g_tracks = ''; + + function setStatus(text) { + if(text != ''){ + $('#status').html( + '15%' + ); + } else { + $('#status').html(''); + } + } + + var Playlist = function() { + } + + var splitText = function(inputtext) { + var words = inputtext + .split(/[ \n\r\t]/) + .map(function(w) { return w.toLowerCase().trim().replace(/^[.,-]+/,'').replace(/[.,-]+$/g,''); }) + .filter(function(w) { return (w.length > 0); }); + words = words.slice(0, 100); + return words; + } + + var refreshText = function() { + setStatus('Loading...'); + + g_name = $('#alltext').val().trim(); + var words = splitText(g_name); + console.log('text changed.', g_name, words); + cache.lookupWords(words, function(worddata) { + setStatus(''); + + console.log('wordcache callback', worddata); + // $('#debug').text(JSON.stringify(worddata, null, 2)); + + var txt = ''; + g_tracks = []; + worddata.forEach(function(data) { + console.log('word', data.word); + + data.tracks.sort(function(a,b) { + return Math.random() - 0.5; + }) + + var names = data.tracks.map(function(track) { + return track.name; + }); + + console.log('names', names); + + var f = new FuzzySet(names); + var fr = f.get(data.word); + console.log('fr', fr); + + var found = null; + var title = ''; + + if (!found) { + data.tracks.forEach(function(track) { + if (track.name.toLowerCase().trim() === data.word.toLowerCase().trim()) { + found = track; + } + }); + } + + if (!found) { + if (fr && fr.length > 0) { + data.tracks.forEach(function(track) { + if (track.name === fr[0][1]) { + found = track; + } + }); + } + } + + if (!found) { + if (data.tracks.length > 0) { + found = data.tracks[0]; + } + } + + console.log('found', found); + if (found) { + g_tracks.push(found.uri); + txt += + '
' + + '' + + '' + + '' + + '
' + + '

'+ + '' + found.name + ''+ + '

' + + 'Album: ' + found.album +''+ + '
Artist: ' + found.artist+'' + + '
' + + '
\n'; + } else { + txt += '
No match found for the word "' + data.word+ '"
\n' + } + }); + + $('#debug').html(txt); + }); + } + + var doSearch = function(word, callback) { + console.log('search for ' + word); + var url = 'https://api.spotify.com/v1/search?type=track&limit=50&q=' + encodeURIComponent('track:"'+word+'"'); + $.ajax(url, { + dataType: 'json', + success: function(r) { + console.log('got track', r); + callback({ + word: word, + tracks: r.tracks.items + .map(function(item) { + var ret = { + name: item.name, + artist: 'Unknown', + artist_uri: '', + album: item.album.name, + album_uri: item.album.uri, + cover_url: '', + uri: item.uri + } + if (item.artists.length > 0) { + ret.artist = item.artists[0].name; + ret.artist_uri = item.artists[0].uri; + } + if (item.album.images.length > 0) { + ret.cover_url = item.album.images[item.album.images.length - 1].url; + } + return ret; + }) + }); + }, + error: function(r) { + callback({ + word: word, + tracks: [] + }); + } + }); + } + + var resolveOneWord = function() { + var word = cache.pop(); + if (word) { + console.log('time to resolve word', word); + setStatus('Looking up the word "' + word + '"'); + doSearch(word, function(result) { + console.log('got word result', result); + cache.store(word, result); + setTimeout(resolveOneWord, 1); + }); + } else { + console.log('no words to look up...'); + setTimeout(resolveOneWord, 1000); + } + } + + var g_access_token = ''; + var g_username = ''; + + var client_id = ''; + var redirect_uri = ''; + + + if (location.host == 'localhost:8888') { + client_id = '2a8909c2980143c496833de968725bf0'; + redirect_uri = 'http://localhost:8888/callback.html'; + } else { + client_id = '2a8909c2980143c496833de968725bf0'; + redirect_uri = 'https://dev.portalurbanna.com.br/callback.html'; + } + + var doLogin = function(callback) { + var url = 'https://accounts.spotify.com/authorize?client_id=' + client_id + + '&response_type=token' + + '&scope=playlist-read-private%20playlist-modify%20playlist-modify-private' + + '&redirect_uri=' + encodeURIComponent(redirect_uri); + localStorage.setItem('createplaylist-tracks', JSON.stringify(g_tracks)); + localStorage.setItem('createplaylist-name', g_name); + var w = window.open(url, 'asdf', 'WIDTH=400, HEIGHT=500'); + } + + var refreshtimer = 0; + var queueRefreshText = function() { + if (refreshtimer) { + clearTimeout(refreshtimer); + } + refreshtimer = setTimeout(function() { + refreshText(); + }, 1000); + } + + exports.startApp = function() { + setStatus(''); + console.log('start app.'); + $('#alltext').keyup(function() { + queueRefreshText(); + }); + $('#alltext').change(function() { + queueRefreshText(); + }); + $('#start').click(function() { + doLogin(function() {}); + }) + $('#alltext').text('hello world'); + refreshText(); + resolveOneWord(); +} + +})(window); \ No newline at end of file diff --git a/assets/js/server.js b/assets/js/server.js new file mode 100644 index 0000000..2a91fde --- /dev/null +++ b/assets/js/server.js @@ -0,0 +1,10 @@ + /* Load the HTTP library */ + var http = require("http"); + + /* Create an HTTP server to handle responses */ + + http.createServer(function(request, response) { + response.writeHead(200, {"Content-Type": "text/plain"}); + response.write("Hello World"); + response.end(); + }).listen(8888); \ No newline at end of file diff --git a/login.md b/login.md new file mode 100644 index 0000000..c73635e --- /dev/null +++ b/login.md @@ -0,0 +1,43 @@ +--- +layout: default +--- + +
+
+

This is an example of the Authorization Code flow

+ Log in with Spotify +
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fd6f8db --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "playlistcreator-example-1", + "lockfileVersion": 2, + "requires": true, + "packages": {} +} From 55074979fe01694e55c59aeec28a51c56d3c02f7 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 20:40:54 -0300 Subject: [PATCH 27/33] Change Index to Login --- callback.md => _pages/callback.md | 0 _pages/create.md | 20 ++++++++++++ login.md => _pages/login.md | 0 index.md | 51 ++++++++++++++++++++++--------- package-lock.json | 8 ++++- package.json | 13 ++++++++ 6 files changed, 77 insertions(+), 15 deletions(-) rename callback.md => _pages/callback.md (100%) create mode 100644 _pages/create.md rename login.md => _pages/login.md (100%) create mode 100644 package.json diff --git a/callback.md b/_pages/callback.md similarity index 100% rename from callback.md rename to _pages/callback.md diff --git a/_pages/create.md b/_pages/create.md new file mode 100644 index 0000000..323c341 --- /dev/null +++ b/_pages/create.md @@ -0,0 +1,20 @@ +--- +layout: default +--- + +
+
+

Write your text here

+
+ +
+
+ +
+
+

Corresponding track names

+
+
+
+
+
\ No newline at end of file diff --git a/login.md b/_pages/login.md similarity index 100% rename from login.md rename to _pages/login.md diff --git a/index.md b/index.md index 323c341..c73635e 100644 --- a/index.md +++ b/index.md @@ -2,19 +2,42 @@ layout: default --- -
-
-

Write your text here

-
- -
-
- +
+
+

This is an example of the Authorization Code flow

+ Log in with Spotify +
+
+
+
+
-
-

Corresponding track names

-
-
-
+
+ + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fd6f8db..f2b97b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,5 +2,11 @@ "name": "playlistcreator-example-1", "lockfileVersion": 2, "requires": true, - "packages": {} + "packages": { + "cookie-parser": "1.3.2", + "express": "~4.16.0", + "cors": "^2.8.4", + "querystring": "~0.2.0", + "request": "~2.83.0" + } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..2d3dda2 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "playlistcreator-example-1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "cookie-parser": "1.3.2", + "express": "~4.16.0", + "cors": "^2.8.4", + "querystring": "~0.2.0", + "request": "~2.83.0" + } + } + \ No newline at end of file From 458495df35ca17eb0fe3d8f7321391e5ad617c00 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 20:44:14 -0300 Subject: [PATCH 28/33] Update Index --- index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.md b/index.md index c73635e..1700461 100644 --- a/index.md +++ b/index.md @@ -2,12 +2,12 @@ layout: default --- -
-
-

This is an example of the Authorization Code flow

- Log in with Spotify +
+
+

This is an example of the Authorization Code flow

+ Log in with Spotify
-
+
From a6badbadf73bd143fb86d831445fedbb0307e539 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 20:45:46 -0300 Subject: [PATCH 29/33] Set Template to Login --- _layouts/default.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_layouts/default.html b/_layouts/default.html index 5bb7339..8a4b131 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -46,6 +46,8 @@

+ + From f431752c8fc7574c219692f7092486d2d84e8243 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 20:52:34 -0300 Subject: [PATCH 30/33] Update Index --- index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.md b/index.md index 1700461..ac8752c 100644 --- a/index.md +++ b/index.md @@ -5,7 +5,7 @@ layout: default

This is an example of the Authorization Code flow

- Log in with Spotify + Log in with Spotify
From e65df239ce59c38b9e618a5ae572e2d2ae563f55 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 21:00:36 -0300 Subject: [PATCH 31/33] Restore to Create Playlist --- _layouts/default.html | 3 +- assets/js/app.js | 367 +++++++++++++++++++++++++----------------- assets/js/login.js | 75 --------- assets/js/main.js | 20 +-- assets/js/new-app.js | 221 ------------------------- index.md | 49 ++---- 6 files changed, 244 insertions(+), 491 deletions(-) delete mode 100644 assets/js/login.js delete mode 100644 assets/js/new-app.js diff --git a/_layouts/default.html b/_layouts/default.html index 8a4b131..8f4fbc3 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -45,9 +45,8 @@

{{content}} + - - diff --git a/assets/js/app.js b/assets/js/app.js index d5aa918..05b58c8 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,148 +1,221 @@ +(function(exports) { -/** - * This is an example of a basic node.js script that performs - * the Authorization Code oAuth2 flow to authenticate against - * the Spotify Accounts. - * - * For more information, read - * https://developer.spotify.com/web-api/authorization-guide/#authorization_code_flow - */ - - var express = require('express'); // Express web server framework - var request = require('request'); // "Request" library - var cors = require('cors'); - var querystring = require('querystring'); - var cookieParser = require('cookie-parser'); - - var client_id = '2a8909c2980143c496833de968725bf0'; // Your client id - var client_secret = '90cbdda3e056407bbd8051d711f6047a'; // Your secret - var redirect_uri = 'https://dev.portalurbanna.com.br/callback'; // Your redirect uri - - /** - * Generates a random string containing numbers and letters - * @param {number} length The length of the string - * @return {string} The generated string - */ - var generateRandomString = function(length) { - var text = ''; - var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - for (var i = 0; i < length; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; - }; - - var stateKey = 'spotify_auth_state'; - - var app = express(); - - app.use(express.static(__dirname + '/public')) - .use(cors()) - .use(cookieParser()); - - app.get('/login', function(req, res) { - - var state = generateRandomString(16); - res.cookie(stateKey, state); - - // your application requests authorization - var scope = 'user-read-private user-read-email'; - res.redirect('https://accounts.spotify.com/authorize?' + - querystring.stringify({ - response_type: 'code', - client_id: client_id, - scope: scope, - redirect_uri: redirect_uri, - state: state - })); - }); - - app.get('/callback', function(req, res) { - - // your application requests refresh and access tokens - // after checking the state parameter - - var code = req.query.code || null; - var state = req.query.state || null; - var storedState = req.cookies ? req.cookies[stateKey] : null; - - if (state === null || state !== storedState) { - res.redirect('/#' + - querystring.stringify({ - error: 'state_mismatch' - })); - } else { - res.clearCookie(stateKey); - var authOptions = { - url: 'https://accounts.spotify.com/api/token', - form: { - code: code, - redirect_uri: redirect_uri, - grant_type: 'authorization_code' - }, - headers: { - 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) - }, - json: true - }; - - request.post(authOptions, function(error, response, body) { - if (!error && response.statusCode === 200) { - - var access_token = body.access_token, - refresh_token = body.refresh_token; - - var options = { - url: 'https://api.spotify.com/v1/me', - headers: { 'Authorization': 'Bearer ' + access_token }, - json: true - }; - - // use the access token to access the Spotify Web API - request.get(options, function(error, response, body) { - console.log(body); - }); - - // we can also pass the token to the browser to make requests from there - res.redirect('/#' + - querystring.stringify({ - access_token: access_token, - refresh_token: refresh_token - })); - } else { - res.redirect('/#' + - querystring.stringify({ - error: 'invalid_token' - })); - } - }); - } - }); - - app.get('/refresh_token', function(req, res) { - - // requesting access token from refresh token - var refresh_token = req.query.refresh_token; - var authOptions = { - url: 'https://accounts.spotify.com/api/token', - headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) }, - form: { - grant_type: 'refresh_token', - refresh_token: refresh_token - }, - json: true - }; - - request.post(authOptions, function(error, response, body) { - if (!error && response.statusCode === 200) { - var access_token = body.access_token; - res.send({ - 'access_token': access_token - }); - } - }); - }); - - console.log('Listening on 8888'); - app.listen(8888); \ No newline at end of file + var cache = new WordCache(); + + var g_name = ''; + var g_tracks = ''; + + function setStatus(text) { + if(text != ''){ + $('#status').html( + '15%' + ); + } else { + $('#status').html(''); + } + } + + var Playlist = function() { + } + + var splitText = function(inputtext) { + var words = inputtext + .split(/[ \n\r\t]/) + .map(function(w) { return w.toLowerCase().trim().replace(/^[.,-]+/,'').replace(/[.,-]+$/g,''); }) + .filter(function(w) { return (w.length > 0); }); + words = words.slice(0, 100); + return words; + } + + var refreshText = function() { + setStatus('Loading...'); + + g_name = $('#alltext').val().trim(); + var words = splitText(g_name); + console.log('text changed.', g_name, words); + cache.lookupWords(words, function(worddata) { + setStatus(''); + + console.log('wordcache callback', worddata); + // $('#debug').text(JSON.stringify(worddata, null, 2)); + + var txt = ''; + g_tracks = []; + worddata.forEach(function(data) { + console.log('word', data.word); + + data.tracks.sort(function(a,b) { + return Math.random() - 0.5; + }) + + var names = data.tracks.map(function(track) { + return track.name; + }); + + console.log('names', names); + + var f = new FuzzySet(names); + var fr = f.get(data.word); + console.log('fr', fr); + + var found = null; + var title = ''; + + if (!found) { + data.tracks.forEach(function(track) { + if (track.name.toLowerCase().trim() === data.word.toLowerCase().trim()) { + found = track; + } + }); + } + + if (!found) { + if (fr && fr.length > 0) { + data.tracks.forEach(function(track) { + if (track.name === fr[0][1]) { + found = track; + } + }); + } + } + + if (!found) { + if (data.tracks.length > 0) { + found = data.tracks[0]; + } + } + + console.log('found', found); + if (found) { + g_tracks.push(found.uri); + txt += + '
' + + '' + + '' + + '' + + '
' + + '

'+ + '' + found.name + ''+ + '

' + + 'Album: ' + found.album +''+ + '
Artist: ' + found.artist+'' + + '
' + + '
\n'; + } else { + txt += '
No match found for the word "' + data.word+ '"
\n' + } + }); + + $('#debug').html(txt); + }); + } + + var doSearch = function(word, callback) { + console.log('search for ' + word); + var url = 'https://api.spotify.com/v1/search?type=track&limit=50&q=' + encodeURIComponent('track:"'+word+'"'); + $.ajax(url, { + dataType: 'json', + success: function(r) { + console.log('got track', r); + callback({ + word: word, + tracks: r.tracks.items + .map(function(item) { + var ret = { + name: item.name, + artist: 'Unknown', + artist_uri: '', + album: item.album.name, + album_uri: item.album.uri, + cover_url: '', + uri: item.uri + } + if (item.artists.length > 0) { + ret.artist = item.artists[0].name; + ret.artist_uri = item.artists[0].uri; + } + if (item.album.images.length > 0) { + ret.cover_url = item.album.images[item.album.images.length - 1].url; + } + return ret; + }) + }); + }, + error: function(r) { + callback({ + word: word, + tracks: [] + }); + } + }); + } + + var resolveOneWord = function() { + var word = cache.pop(); + if (word) { + console.log('time to resolve word', word); + setStatus('Looking up the word "' + word + '"'); + doSearch(word, function(result) { + console.log('got word result', result); + cache.store(word, result); + setTimeout(resolveOneWord, 1); + }); + } else { + console.log('no words to look up...'); + setTimeout(resolveOneWord, 1000); + } + } + + var g_access_token = ''; + var g_username = ''; + + var client_id = ''; + var redirect_uri = ''; + + + if (location.host == 'localhost:8888') { + client_id = '2a8909c2980143c496833de968725bf0'; + redirect_uri = 'http://localhost:8888/callback.html'; + } else { + client_id = '2a8909c2980143c496833de968725bf0'; + redirect_uri = 'https://dev.portalurbanna.com.br/callback.html'; + } + + var doLogin = function(callback) { + var url = 'https://accounts.spotify.com/authorize?client_id=' + client_id + + '&response_type=token' + + '&scope=playlist-read-private%20playlist-modify%20playlist-modify-private' + + '&redirect_uri=' + encodeURIComponent(redirect_uri); + localStorage.setItem('createplaylist-tracks', JSON.stringify(g_tracks)); + localStorage.setItem('createplaylist-name', g_name); + var w = window.open(url, 'asdf', 'WIDTH=400, HEIGHT=500'); + } + + var refreshtimer = 0; + var queueRefreshText = function() { + if (refreshtimer) { + clearTimeout(refreshtimer); + } + refreshtimer = setTimeout(function() { + refreshText(); + }, 1000); + } + + exports.startApp = function() { + setStatus(''); + console.log('start app.'); + $('#alltext').keyup(function() { + queueRefreshText(); + }); + $('#alltext').change(function() { + queueRefreshText(); + }); + $('#start').click(function() { + doLogin(function() {}); + }) + $('#alltext').text('hello world'); + refreshText(); + resolveOneWord(); +} + +})(window); \ No newline at end of file diff --git a/assets/js/login.js b/assets/js/login.js deleted file mode 100644 index 76044ad..0000000 --- a/assets/js/login.js +++ /dev/null @@ -1,75 +0,0 @@ -(function() { - - /** - * Obtains parameters from the hash of the URL - * @return Object - */ - - function getHashParams() { - var hashParams = {}; - var e, r = /([^&;=]+)=?([^&;]*)/g, - q = window.location.hash.substring(1); - while ( e = r.exec(q)) { - hashParams[e[1]] = decodeURIComponent(e[2]); - } - return hashParams; - } - - var userProfileSource = document.getElementById('user-profile-template').innerHTML, - userProfileTemplate = Handlebars.compile(userProfileSource), - userProfilePlaceholder = document.getElementById('user-profile'); - - var oauthSource = document.getElementById('oauth-template').innerHTML, - oauthTemplate = Handlebars.compile(oauthSource), - oauthPlaceholder = document.getElementById('oauth'); - - var params = getHashParams(); - - var access_token = params.access_token, - refresh_token = params.refresh_token, - error = params.error; - - if (error) { - alert('There was an error during the authentication'); - } else { - if (access_token) { - // render oauth info - oauthPlaceholder.innerHTML = oauthTemplate({ - access_token: access_token, - refresh_token: refresh_token - }); - - $.ajax({ - url: 'https://api.spotify.com/v1/me', - headers: { - 'Authorization': 'Bearer ' + access_token - }, - success: function(response) { - userProfilePlaceholder.innerHTML = userProfileTemplate(response); - - $('#login').hide(); - $('#loggedin').show(); - } - }); - } else { - // render initial screen - $('#login').show(); - $('#loggedin').hide(); - } - - document.getElementById('obtain-new-token').addEventListener('click', function() { - $.ajax({ - url: '/refresh_token', - data: { - 'refresh_token': refresh_token - } - }).done(function(data) { - access_token = data.access_token; - oauthPlaceholder.innerHTML = oauthTemplate({ - access_token: access_token, - refresh_token: refresh_token - }); - }); - }, false); - } - })(); \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index 2231f37..eb17042 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,8 +1,8 @@ var g_access_token = ""; - var g_username = ""; - var g_tracks = []; +var g_username = ""; +var g_tracks = []; - function getUsername(callback) { +function getUsername(callback) { console.log("getUsername"); var url = "https://api.spotify.com/v1/me"; $.ajax(url, { @@ -18,9 +18,9 @@ var g_access_token = ""; callback(null); } }); - } +} - function createPlaylist(username, name, callback) { +function createPlaylist(username, name, callback) { console.log("createPlaylist", username, name); var url = "https://api.spotify.com/v1/users/" + username + "/playlists"; $.ajax(url, { @@ -42,9 +42,9 @@ var g_access_token = ""; callback(null); } }); - } +} - function addTracksToPlaylist(username, playlist, tracks, callback) { +function addTracksToPlaylist(username, playlist, tracks, callback) { console.log("addTracksToPlaylist", username, playlist, tracks); var url = "https://api.spotify.com/v1/users/" + @@ -68,9 +68,9 @@ var g_access_token = ""; callback(null); } }); - } +} - function doit() { +function doit() { // parse hash var hash = location.hash.replace(/#/g, ""); var all = hash.split("&"); @@ -109,4 +109,4 @@ var g_access_token = ""; }); }); }); - } \ No newline at end of file +} \ No newline at end of file diff --git a/assets/js/new-app.js b/assets/js/new-app.js deleted file mode 100644 index 05b58c8..0000000 --- a/assets/js/new-app.js +++ /dev/null @@ -1,221 +0,0 @@ -(function(exports) { - - var cache = new WordCache(); - - var g_name = ''; - var g_tracks = ''; - - function setStatus(text) { - if(text != ''){ - $('#status').html( - '15%' - ); - } else { - $('#status').html(''); - } - } - - var Playlist = function() { - } - - var splitText = function(inputtext) { - var words = inputtext - .split(/[ \n\r\t]/) - .map(function(w) { return w.toLowerCase().trim().replace(/^[.,-]+/,'').replace(/[.,-]+$/g,''); }) - .filter(function(w) { return (w.length > 0); }); - words = words.slice(0, 100); - return words; - } - - var refreshText = function() { - setStatus('Loading...'); - - g_name = $('#alltext').val().trim(); - var words = splitText(g_name); - console.log('text changed.', g_name, words); - cache.lookupWords(words, function(worddata) { - setStatus(''); - - console.log('wordcache callback', worddata); - // $('#debug').text(JSON.stringify(worddata, null, 2)); - - var txt = ''; - g_tracks = []; - worddata.forEach(function(data) { - console.log('word', data.word); - - data.tracks.sort(function(a,b) { - return Math.random() - 0.5; - }) - - var names = data.tracks.map(function(track) { - return track.name; - }); - - console.log('names', names); - - var f = new FuzzySet(names); - var fr = f.get(data.word); - console.log('fr', fr); - - var found = null; - var title = ''; - - if (!found) { - data.tracks.forEach(function(track) { - if (track.name.toLowerCase().trim() === data.word.toLowerCase().trim()) { - found = track; - } - }); - } - - if (!found) { - if (fr && fr.length > 0) { - data.tracks.forEach(function(track) { - if (track.name === fr[0][1]) { - found = track; - } - }); - } - } - - if (!found) { - if (data.tracks.length > 0) { - found = data.tracks[0]; - } - } - - console.log('found', found); - if (found) { - g_tracks.push(found.uri); - txt += - '
' + - '' + - '' + - '' + - '
' + - '

'+ - '' + found.name + ''+ - '

' + - 'Album: ' + found.album +''+ - '
Artist: ' + found.artist+'' + - '
' + - '
\n'; - } else { - txt += '
No match found for the word "' + data.word+ '"
\n' - } - }); - - $('#debug').html(txt); - }); - } - - var doSearch = function(word, callback) { - console.log('search for ' + word); - var url = 'https://api.spotify.com/v1/search?type=track&limit=50&q=' + encodeURIComponent('track:"'+word+'"'); - $.ajax(url, { - dataType: 'json', - success: function(r) { - console.log('got track', r); - callback({ - word: word, - tracks: r.tracks.items - .map(function(item) { - var ret = { - name: item.name, - artist: 'Unknown', - artist_uri: '', - album: item.album.name, - album_uri: item.album.uri, - cover_url: '', - uri: item.uri - } - if (item.artists.length > 0) { - ret.artist = item.artists[0].name; - ret.artist_uri = item.artists[0].uri; - } - if (item.album.images.length > 0) { - ret.cover_url = item.album.images[item.album.images.length - 1].url; - } - return ret; - }) - }); - }, - error: function(r) { - callback({ - word: word, - tracks: [] - }); - } - }); - } - - var resolveOneWord = function() { - var word = cache.pop(); - if (word) { - console.log('time to resolve word', word); - setStatus('Looking up the word "' + word + '"'); - doSearch(word, function(result) { - console.log('got word result', result); - cache.store(word, result); - setTimeout(resolveOneWord, 1); - }); - } else { - console.log('no words to look up...'); - setTimeout(resolveOneWord, 1000); - } - } - - var g_access_token = ''; - var g_username = ''; - - var client_id = ''; - var redirect_uri = ''; - - - if (location.host == 'localhost:8888') { - client_id = '2a8909c2980143c496833de968725bf0'; - redirect_uri = 'http://localhost:8888/callback.html'; - } else { - client_id = '2a8909c2980143c496833de968725bf0'; - redirect_uri = 'https://dev.portalurbanna.com.br/callback.html'; - } - - var doLogin = function(callback) { - var url = 'https://accounts.spotify.com/authorize?client_id=' + client_id + - '&response_type=token' + - '&scope=playlist-read-private%20playlist-modify%20playlist-modify-private' + - '&redirect_uri=' + encodeURIComponent(redirect_uri); - localStorage.setItem('createplaylist-tracks', JSON.stringify(g_tracks)); - localStorage.setItem('createplaylist-name', g_name); - var w = window.open(url, 'asdf', 'WIDTH=400, HEIGHT=500'); - } - - var refreshtimer = 0; - var queueRefreshText = function() { - if (refreshtimer) { - clearTimeout(refreshtimer); - } - refreshtimer = setTimeout(function() { - refreshText(); - }, 1000); - } - - exports.startApp = function() { - setStatus(''); - console.log('start app.'); - $('#alltext').keyup(function() { - queueRefreshText(); - }); - $('#alltext').change(function() { - queueRefreshText(); - }); - $('#start').click(function() { - doLogin(function() {}); - }) - $('#alltext').text('hello world'); - refreshText(); - resolveOneWord(); -} - -})(window); \ No newline at end of file diff --git a/index.md b/index.md index ac8752c..323c341 100644 --- a/index.md +++ b/index.md @@ -3,41 +3,18 @@ layout: default ---
-
-

This is an example of the Authorization Code flow

- Log in with Spotify -
-
-
-
- -
-
- - - - \ No newline at end of file +
+

Corresponding track names

+
+
+
+
+

\ No newline at end of file From 5eecb76738d70c59891ed6df9f561ccf46b8dc8b Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 21:04:06 -0300 Subject: [PATCH 32/33] Set Callback.html --- _pages/callback.md | 1 + 1 file changed, 1 insertion(+) diff --git a/_pages/callback.md b/_pages/callback.md index c709a42..a70d826 100644 --- a/_pages/callback.md +++ b/_pages/callback.md @@ -1,5 +1,6 @@ --- layout: default +permalink: "/callback.html" ---
From da8c9ea73527c2847ac7f96d9e346a6935531544 Mon Sep 17 00:00:00 2001 From: "Camila L. Oliveira" Date: Tue, 22 Feb 2022 21:26:30 -0300 Subject: [PATCH 33/33] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..2a6afc1 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +dev.portalurbanna.com.br