diff --git a/images/No_Image_Available.jpg b/images/No_Image_Available.jpg new file mode 100644 index 0000000..1caf419 Binary files /dev/null and b/images/No_Image_Available.jpg differ diff --git a/images/backdrop.png b/images/backdrop.png new file mode 100644 index 0000000..4609bb3 Binary files /dev/null and b/images/backdrop.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..74c39f7 --- /dev/null +++ b/index.html @@ -0,0 +1,69 @@ + + + + + + + + + + + Responsive News Reader + + + +
+

Fetch News

+ + +
+ + + +
+ + +
+
+
+
+ +
Sort results by:
+ + + + + + + +
+
+
+ +
+ + + +
+ + + + + diff --git a/src/scripts.js b/src/scripts.js new file mode 100644 index 0000000..049c548 --- /dev/null +++ b/src/scripts.js @@ -0,0 +1,225 @@ +// News API key: 9ed005ef4eb94baf913fce701c69972f + +//TODO: stylization, excludeDomains/blocklist + +//Object contains possible URLS for fetch requests +const apiRequests = { + usTop20: 'https://newsapi.org/v2/top-headlines?country=us&apiKey=9ed005ef4eb94baf913fce701c69972f', + + ukTop20: 'https://newsapi.org/v2/top-headlines?country=gb&apiKey=9ed005ef4eb94baf913fce701c69972f', + + +//object contains stored parameters for searches + customParameters : { + q: {val: null, string: ""}, + from: {val: null, string: ""}, + excludeDomains: {val: null, string: ""}, + language: {val: null, string: ""}, + page: {val: 1, string: ""}, + sortBy: {val: null, string: ""} + }, + + +//getURL provides a url for API with specified parameters above + getURL: function(){ + const customURL = `https://newsapi.org/v2/everything?${this.customParameters.q.string}${this.customParameters.from.string}${this.customParameters.excludeDomains.string}${this.customParameters.page.string}${this.customParameters.language.string}${this.customParameters.sortBy.string}&apiKey=9ed005ef4eb94baf913fce701c69972f` + return customURL + }, + + updateURL: function(parameter, update){ + this.customParameters[parameter].val = update + if (parameter === "q"){ + this.customParameters[parameter].string = `${parameter}=${update}` + }else{ + this.customParameters[parameter].string = `&${parameter}=${update}` + } + }, + + + blockList: [] + +} + + +//Object contains functions attached to buttons and page features +const pageHandlers = { + + changeCountry: function(){ + const countryButtonElement = document.querySelector(".country-button") + countryButtonElement.addEventListener("click", event => { + paginationElement.style.display = "none"; + countryButtonElement.textContent === "Top UK Stories" + ? ( countryButtonElement.textContent = "Top US Stories", + fetchNews(apiRequests.ukTop20)) + : (countryButtonElement.textContent = "Top UK Stories", + fetchNews(apiRequests.usTop20)) + }) + }, + + search: function(event){ + event.preventDefault(); + if (searchTextElement.value === ""){ + return + } + const dateRange = formatDate(dateRangeElement.value) + paginationElement.style.display = "inline" + apiRequests.updateURL("q", searchTextElement.value) + apiRequests.updateURL("from", dateRange) + fetchNews(apiRequests.getURL()) + searchTextElement.value = "" + pageHandlers.checkPage() + }, + + + checkPage: function(){ + apiRequests.customParameters.page.val === 1 + ? prevPageElement.disabled = true + : prevPageElement.disabled = false + }, + + + paginationControl: function(event){ + let currentPage = apiRequests.customParameters.page.val + event.target.value === "next" + ? currentPage ++ + : currentPage -- + apiRequests.updateURL("page", currentPage) + fetchNews(apiRequests.getURL()) + pageHandlers.checkPage() + + +}, + + updateAdvancedOptions: event => { + event.preventDefault() + apiRequests.updateURL("language", languageSelector.value) + apiRequests.updateURL("sortBy", sortBySelector.value ) + pageHandlers.search(event) +}, + + openCloseAdvanced: event => { + advancedSearchMenu.style.display === "none" + ? advancedSearchMenu.style.display = "block" + : advancedSearchMenu.style.display = "none" +}, + + blockSource: event => { + const host = linkElement.hostname + console.log(host) + } + +} + +function assignElement(elementType, elementClassName, elementTextContent){ + const output = document.createElement(elementType) + output.className = elementClassName + output.textContent = elementTextContent + return output +} + + +//creates each story panel from fetch response +function createStoryPanel(article){ + const storyDivElement = assignElement("div", "story-div-element") + const headerElement = assignElement("div", "story-header") + const headerLeftElement = assignElement("div", "story-header-left") + const publicationInfoElement = assignElement("span", "publication-info") + const headlineElement = assignElement("h2", "headline", article.title) + const publicationNameElement = assignElement("h4", "publication-name", article.source.name) + const publicationDateElement = assignElement("h4", "publication-date", convertDateForDisplay(article.publishedAt)) + const blockButtonElement = assignElement("button", "block-button", "Block") + const storyImageElement = assignElement("img", "story-image") + storyImageElement.setAttribute("src", article.urlToImage) + if (storyImageElement.src === "http://127.0.0.1:3000/null"){ + storyImageElement.setAttribute("src", "../images/No_Image_Available.jpg") + } + const descriptionElement = assignElement("p", "description-element", article.description) + const linkElement = assignElement("a", "full-story-link", "See full story") + linkElement.setAttribute("href", article.url) + blockButtonElement.addEventListener("click", event => { + let host = linkElement.hostname + host = host.replace("www.", "") + apiRequests.blockList.push(host) + const blockedDomains = apiRequests.blockList.join(",") + apiRequests.updateURL("excludeDomains", blockedDomains) + fetchNews(apiRequests.getURL()) + }) + + publicationInfoElement.appendChild(publicationNameElement) + publicationInfoElement.appendChild(publicationDateElement) + + headerLeftElement.appendChild(headlineElement) + headerLeftElement.appendChild(publicationInfoElement) + headerLeftElement.appendChild(blockButtonElement) + + headerElement.appendChild(headerLeftElement) + headerElement.appendChild(storyImageElement) + + storyDivElement.appendChild(headerElement) + storyDivElement.appendChild(descriptionElement) + storyDivElement.appendChild(linkElement) + newsDisplayElement.appendChild(storyDivElement) +} + + + +//formats date for search +function formatDate(monthsAgo){ + if ( monthsAgo != "all-time") { + const newDate = new Date + newDate.setMonth(newDate.getMonth() - monthsAgo); + const isoDate = newDate.toISOString().slice(0,10) + return `${isoDate}` + }else{ + return null + } +} + +//formats date for display + +function convertDateForDisplay(date){ + const inputDate = new Date(date) + return inputDate.toLocaleString() +} + + +function fetchNews(apiAddress){ + fetch(apiAddress) + .then(response => { + return response.json() + }) + .then(body => { + newsDisplayElement.innerHTML = "" + body.articles.forEach(article => { + createStoryPanel(article) + }) + }) +} + + +const contentElement = document.querySelector(".content") +const newsDisplayElement = document.querySelector(".news-display") +const paginationElement = document.querySelector(".pagination") +const nextPageElement = document.querySelector(".next-page") +const prevPageElement = document.querySelector(".back-page") +const searchFormElement = document.querySelector(".search-form") +const searchTextElement = document.querySelector(".search-text") +const dateRangeElement = document.querySelector(".date-range") +const advancedSearchElement = document.querySelector(".advanced-search") +const advancedSearchMenu = document.querySelector(".advanced-search-options") +const advancedSearchForm = document.querySelector(".advanced-search-form") +const languageSelector = document.querySelector(".language-select") +const sortBySelector = document.querySelector(".sort-by-select") +searchFormElement.addEventListener("submit", pageHandlers.search) +prevPageElement.addEventListener("click", pageHandlers.paginationControl) +nextPageElement.addEventListener("click", pageHandlers.paginationControl) +advancedSearchElement.addEventListener("click", pageHandlers.openCloseAdvanced) +advancedSearchForm.addEventListener("submit", pageHandlers.updateAdvancedOptions) + + + + + + +fetchNews(apiRequests.usTop20) +pageHandlers.changeCountry() diff --git a/styles/styles.css b/styles/styles.css new file mode 100644 index 0000000..d646d4f --- /dev/null +++ b/styles/styles.css @@ -0,0 +1,104 @@ +body { + margin: 0; + display: flex; + flex-direction: column; + height: 100vh; + +} + +header{ + background-color: #e7eaef; + height: 140px; + font-family: 'Source Serif Pro', serif; + border-bottom: 1px black solid + +} + + +.content{ + background: #E9EEF2 url("../images/backdrop.png") repeat-x; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; +} + +.advanced-search-options{ + display: none; + width: 100vw; + height:80px; + background-color: white; +} + +.advanced-search-form{ + display: flex; + flex-direction: row; +} + +.news-display{ + background: #E9EEF2 url("../images/backdrop.png") repeat-x; + + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; + +} + +.pagination{ + display: none; +} + +.headline{ + margin-right: 1em; + font-family: 'Source Serif Pro', serif; + +} + + +.story-div-element{ + background-color: #e7eaef; + margin: 1em; + border: black 1px solid; + padding: 0.5em; + width: 90vw; +} + +.story-header{ + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.story-image{ + display: none; +} + + +.publication-info{ + display:flex; + flex-direction: row; + justify-content: space-between; + margin-right: 2em; +} + + +/* Medium devices (landscape tablets, 768px and up) */ +@media only screen and (min-width: 768px) { + .story-image{ + display: inline; + width: 40vw; + height: auto; + align-self: flex-end; + } + + +} + +/* Large devices (laptops/desktops, 992px and up) */ +@media only screen and (min-width: 992px) { + + +}