From f1e1ecc2ac42e2c0ce1da14b589f4c526c0324f4 Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Mon, 10 Feb 2025 13:31:06 +0100 Subject: [PATCH] ui fixes --- src/routes/data-disclaimer/+page.svelte | 96 +++--- src/routes/economic-calendar/+page.svelte | 358 ++++++++++------------ src/routes/sitemap.xml/+server.ts | 48 ++- 3 files changed, 244 insertions(+), 258 deletions(-) diff --git a/src/routes/data-disclaimer/+page.svelte b/src/routes/data-disclaimer/+page.svelte index 06761298..540cb2ed 100644 --- a/src/routes/data-disclaimer/+page.svelte +++ b/src/routes/data-disclaimer/+page.svelte @@ -7,8 +7,8 @@

- The data on this site is for informational purposes only and - should not be used for trading or investment decisions. + The financial data presented on Stocknear is provided for + informational and educational purposes only. This information + should not be construed as financial advice or used as the sole + basis for trading or investment decisions.

- We strive for accuracy and quality. If you spot errors, please contact - us via + We maintain rigorous standards for data accuracy and quality assurance. + If you identify any discrepancies or require clarification, please + reach out to us via Discord - or email us at + or contact our support team at

- Stock Charts + Market Data & Stock Charts

- Intraday and historical stock chart data are provided by + Our comprehensive market data and real-time stock charts are + powered by - FMP - . + Financial Modeling Prep (FMP) + , a leading provider of financial data services. This + includes intraday pricing, historical data, and technical + indicators.

- Stocks Covered + Market Coverage

- Stocknear covers all stocks and ETFs listed on major US - exchanges (NASDAQ, NYSE, NYSE American, NYSE Arca) and many OTC - stocks. We also include international companies trading on US - exchanges and aim to add global stock exchanges soon. + Stocknear provides comprehensive coverage of securities listed + on major U.S. exchanges, including NASDAQ, NYSE, NYSE American, + and NYSE Arca. Our database encompasses stocks, ETFs, ADRs, and + select OTC securities. We are actively expanding our coverage to + include additional global exchanges and financial instruments.

- Historical Financials + Financial Fundamentals

- Fundamental financial data is sourced from + Our fundamental financial data is sourced from - FMP - , based on official SEC filings (10-K, 10-Q). Data is - updated daily, but delays may occur if companies file late. All - displayed data is official, audited, and SEC-submitted. + Financial Modeling Prep + + and is derived from official SEC filings (Forms 10-K, 10-Q, and 8-K). + While our systems update this information daily, there may be inherent + delays due to standard SEC filing windows and corporate reporting + schedules. All financial data presented is based on official, audited + company submissions to regulatory authorities.

- Price Targets and Analyst Estimates + Analyst Coverage & Market Projections

- Price targets and ratings are from Benzinga, sourced from Wall - Street analysts. Revenue and EPS forecasts come from Benzinga - and - FMP - . Analyst accuracy and performance metrics are also provided - by Benzinga. Differences across sources may occur but are - directionally consistent. Use this information cautiously as - predictions are often inaccurate. + Financial Modeling Prep + . While we strive to present the most accurate forecasts, + users should note that market predictions are inherently + uncertain and should be considered as part of a broader + investment research process.

- ETF Holdings + ETF Analytics

- Most ETF holdings data is provided by - FMP - . + Financial Modeling Prep + , ensuring accurate and timely information on fund + compositions, allocations, and performance metrics.

- Options Data + Options Market Data

- Options flow data is sourced from Benzinga via OPRA. Individual - ticker options data is from Intrinio, also sourced from OPRA. + Our options market data infrastructure combines real-time + options flow information from Benzinga and individual options + chain data from Intrinio, both sourced through the Options Price + Reporting Authority (OPRA). This provides comprehensive coverage + of options market activity and pricing across all major + exchanges.

diff --git a/src/routes/economic-calendar/+page.svelte b/src/routes/economic-calendar/+page.svelte index c71d9317..fafe1d46 100644 --- a/src/routes/economic-calendar/+page.svelte +++ b/src/routes/economic-calendar/+page.svelte @@ -18,9 +18,11 @@ let filterList = []; let weekdayFiltered = []; - let weekday; // Added declaration + let weekday = []; // our unordered week data let syncWorker: Worker | undefined; - let pagePathName = $page?.url?.pathname; + let pagePathName: string = ""; + // reassign pagePathName reactively + $: pagePathName = $page?.url?.pathname || ""; const maxWeeksChange = 6; const today = new Date(); @@ -31,21 +33,27 @@ let sortMode = false; $: testList = []; + // Get calendar data from our load function $: economicCalendar = data?.getEconomicCalendar; + // Calculate the week days $: daysOfWeek = getDaysOfWeek(currentWeek); + // Format days for header labels $: formattedWeekday = daysOfWeek.map((day) => format(day.date, "EEE, MMM d")); - $: { - if (!sortMode) { - weekday = getWeekdayData(economicCalendar, daysOfWeek); - rawData = weekday; - } - // Reapply filters whenever weekday data changes - if (filterList.length > 0 && syncWorker) { - loadWorker(); - } + // Recalculate weekday data when the economicCalendar or days change – but only when not sorting + $: if (!sortMode) { + weekday = getWeekdayData(economicCalendar, daysOfWeek); + rawData = weekday; } + // Whenever filters are applied and the worker exists, trigger filtering + $: if (filterList.length > 0 && syncWorker) { + loadWorker(); + } + + // Create a consolidated derived value for our header and table rendering + $: displayWeekData = filterList.length === 0 ? weekday : weekdayFiltered; + const startBoundary = subWeeks( startOfWeek(today, { weekStartsOn: 1 }), maxWeeksChange, @@ -54,13 +62,13 @@ startOfWeek(today, { weekStartsOn: 1 }), maxWeeksChange, ); - $: previousMax = currentWeek <= startBoundary; $: nextMax = currentWeek >= endBoundary; let currentDate = new Date(); let selectedWeekday = Math.min((currentDate.getDay() + 6) % 7, 4); + // Returns an array of weekdays (Monday - Friday) for a given week. function getDaysOfWeek(week) { const startDate = startOfWeek(week, { weekStartsOn: 1 }); return Array.from({ length: 5 }, (_, i) => ({ @@ -69,23 +77,27 @@ })); } + // Retrieves and sorts calendar data for each day. function getWeekdayData(calendar, days) { if (!calendar) return []; return days.map((day) => { const dayData = calendar.filter( (item) => item.date === format(day.date, "yyyy-MM-dd"), ); - return dayData?.sort( + return dayData.sort( (a, b) => - new Date(`1970-01-01T${a.time}`) - new Date(`1970-01-01T${b.time}`), + new Date(`1970-01-01T${a.time}`).getTime() - + new Date(`1970-01-01T${b.time}`).getTime(), ); }); } + // Handle messages from our filtering web worker. const handleMessage = (event) => { weekdayFiltered = event.data?.finalData?.output ?? []; }; + // Tell the web worker to filter our data const loadWorker = async () => { syncWorker?.postMessage({ rawData, filterList }); }; @@ -115,7 +127,6 @@ state === "previous" ? subWeeks(currentWeek, 1) : addWeeks(currentWeek, 1); - if (newWeek >= startBoundary && newWeek <= endBoundary) { currentWeek = newWeek; } @@ -123,21 +134,21 @@ function saveRules() { try { - // Save the version along with the rules - localStorage?.setItem(pagePathName, JSON?.stringify(filterList)); + localStorage?.setItem(pagePathName, JSON.stringify(filterList)); } catch (e) { - console.log("Failed saving filterlist: ", e); + console.error("Failed saving filterlist:", e); } } + // Notice we now initialize checkedItems just once instead of using a reactive assignment. + let checkedItems: Set = new Set(); + onMount(async () => { try { const savedRules = localStorage?.getItem(pagePathName); - if (savedRules) { filterList = JSON.parse(savedRules); } - checkedItems = new Set(filterList); if (!syncWorker) { @@ -146,29 +157,23 @@ syncWorker.onmessage = handleMessage; } } catch (e) { - console.log(e); + console.error(e); } }); - function handleInput(event) { - const searchQuery = event.target.value?.toLowerCase() || ""; - + // Update the global searchQuery (avoid shadowing) and debounce the filtering. + function handleInput(event: InputEvent) { + searchQuery = (event.target as HTMLInputElement)?.value.toLowerCase() || ""; setTimeout(() => { testList = []; - if (searchQuery.length > 0) { - const rawList = listOfRelevantCountries; - testList = - rawList?.filter((item) => { - const index = item?.toLowerCase(); - return index?.startsWith(searchQuery); - }) || []; + testList = listOfRelevantCountries.filter((item) => + item.toLowerCase().startsWith(searchQuery), + ); } }, 50); } - $: checkedItems = new Set(); - async function handleChangeValue(value) { if (checkedItems.has(value)) { checkedItems.delete(value); @@ -190,23 +195,20 @@ function handleReset() { filterList = []; checkedItems = new Set(); - economicCalendar = data?.getEconomicCalendar; daysOfWeek = getDaysOfWeek(currentWeek); formattedWeekday = daysOfWeek.map((day) => format(day.date, "EEE, MMM d")); weekday = getWeekdayData(economicCalendar, daysOfWeek); rawData = weekday; - currentWeek = startOfWeek(today, { weekStartsOn: 1 }); selectedWeekday = Math.min((currentDate.getDay() + 6) % 7, 4); - previousMax = currentWeek <= startBoundary; nextMax = currentWeek >= endBoundary; - saveRules(); } - let columns = [ + // Static columns (do not change across renders) + const columns = [ { key: "time", label: "Time", align: "left" }, { key: "country", label: "Country", align: "left" }, { key: "event", label: "Event", align: "left" }, @@ -228,86 +230,70 @@ const sortData = (key) => { sortMode = true; - for (const k in sortOrders) { - if (k !== key) { - sortOrders[k].order = "none"; - } - } + Object.keys(sortOrders).forEach((k) => { + if (k !== key) sortOrders[k].order = "none"; + }); - // Cycle through 'none', 'asc', 'desc' for the clicked key + // Cycle through "none", "asc", "desc" const orderCycle = ["none", "asc", "desc"]; const currentOrderIndex = orderCycle.indexOf( sortOrders[key]?.order || "none", ); sortOrders[key] = { - ...(sortOrders[key] || {}), + ...sortOrders[key], order: orderCycle[(currentOrderIndex + 1) % orderCycle.length], }; - const sortOrder = sortOrders[key]?.order; - // Reset to original data when 'none' and stop further sorting + const sortOrder = sortOrders[key].order; if (sortOrder === "none") { sortMode = false; return; } - // Generic comparison function const compareValues = (a, b) => { const { type } = sortOrders[key]; let valueA, valueB; - switch (type) { case "date": - valueA = new Date(a[key]); - valueB = new Date(b[key]); + valueA = new Date(a[key]).getTime(); + valueB = new Date(b[key]).getTime(); break; - case "rating": case "string": - // Retrieve values - valueA = a[key]; - valueB = b[key]; - - // Handle null or undefined values, always placing them at the bottom - if (valueA == null && valueB == null) { - return 0; // Both are null/undefined, no need to change the order - } else if (valueA == null) { - return 1; // null goes to the bottom - } else if (valueB == null) { - return -1; // null goes to the bottom - } - - // Convert the values to uppercase for case-insensitive comparison - valueA = valueA?.toUpperCase(); - valueB = valueB?.toUpperCase(); - - // Perform the sorting based on ascending or descending order + valueA = a[key] ? a[key].toUpperCase() : ""; + valueB = b[key] ? b[key].toUpperCase() : ""; return sortOrder === "asc" - ? valueA?.localeCompare(valueB) - : valueB?.localeCompare(valueA); + ? valueA.localeCompare(valueB) + : valueB.localeCompare(valueA); case "number": default: valueA = parseFloat(a[key]); valueB = parseFloat(b[key]); break; } - - if (sortOrder === "asc") { - return valueA < valueB ? -1 : valueA > valueB ? 1 : 0; - } else { - return valueA > valueB ? -1 : valueA < valueB ? 1 : 0; - } + return sortOrder === "asc" + ? valueA < valueB + ? -1 + : valueA > valueB + ? 1 + : 0 + : valueA > valueB + ? -1 + : valueA < valueB + ? 1 + : 0; }; - // Sort and update the originalData and stockList - weekday[selectedWeekday] = [...rawData[selectedWeekday]].sort( - compareValues, - ); + // Create a new array copy to trigger reactivity (instead of in-place mutation) + weekday = [ + ...weekday.slice(0, selectedWeekday), + [...rawData[selectedWeekday]].sort(compareValues), + ...weekday.slice(selectedWeekday + 1), + ]; }; @@ -337,11 +323,10 @@
- +
- - {#each filterList?.length === 0 ? weekday : weekdayFiltered as day, index} + {#each displayWeekData as day, index (formattedWeekday[index])}
{formattedWeekday[index]} - - {day?.length} Events{day?.length} Events
@@ -424,14 +412,16 @@ class="w-6 h-6 m-auto" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" - > + + /> +
+
@@ -467,32 +457,28 @@ class="relative sticky z-40 focus:outline-none -top-1" tabindex="0" role="menu" - style="" >
- {#each testList.length > 0 && searchQuery?.length > 0 ? testList : searchQuery?.length > 0 && testList?.length === 0 ? [] : listOfRelevantCountries as item} + {#each searchQuery.length > 0 ? testList : listOfRelevantCountries as item}
@@ -507,7 +493,7 @@
{#each [1, 2, 3] as i}
@@ -589,34 +559,35 @@ - {#if filterList?.length !== 0} + {#if filterList.length !== 0} {/if}
+
- {#each filterList?.length === 0 ? weekday : weekdayFiltered as day, index} + {#each displayWeekData as day, index} {#if index === selectedWeekday} {#if day?.length !== 0}
@@ -624,19 +595,15 @@ {formattedWeekday[index]?.split(", ")[1]} · {day?.length} Events - {#if filterList?.length !== 0} + {#if filterList.length !== 0}
Filters - {filterList?.length} + {filterList.length}
{/if} @@ -651,16 +618,14 @@ {#each day as item} - - + - @@ -670,93 +635,98 @@ class="w-4 h-4 sm:w-6 sm:h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" - > + + + + + + + /> + + {:else if item?.country === "UK"} + + + + + + + + /> + + {:else} {item?.country} flag {/if} - - {item?.country} - + {item?.country} - {item?.event?.length > 40 - ? item?.event?.slice(0, 40) + "..." + ? item?.event.slice(0, 40) + "..." : item?.event} - - {item?.actual !== (null || "") + {item?.actual !== null && item?.actual !== "" ? abbreviateNumber(item?.actual) : "-"} - - {item?.consensus !== (null || "") + {item?.consensus !== null && + item?.consensus !== "" ? abbreviateNumber(item?.consensus) : "-"} - - {item?.prior !== (null || "") + {item?.prior !== null && item?.prior !== "" ? abbreviateNumber(item?.prior) : "-"} - @@ -818,7 +788,7 @@ class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer bg-inherit sm:hover:bg-secondary transition ease-out duration-100" >
@@ -827,9 +797,9 @@
- - Get the latest Earnings of companies - + Get the latest Earnings of companies
@@ -837,7 +807,7 @@ class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer bg-inherit sm:hover:bg-secondary transition ease-out duration-100" >
@@ -846,9 +816,9 @@
- - Get the latest dividend announcement - + Get the latest dividend announcement
diff --git a/src/routes/sitemap.xml/+server.ts b/src/routes/sitemap.xml/+server.ts index 46c0f71a..490ef2d8 100644 --- a/src/routes/sitemap.xml/+server.ts +++ b/src/routes/sitemap.xml/+server.ts @@ -3,7 +3,7 @@ import { convertToSlug } from "$lib/utils"; const pages = [ { title: "/" }, { title: "/reddit-tracker" }, - { title: "/list/most-shorted-stocks"}, + { title: "/list/most-shorted-stocks" }, { title: "/stocks" }, { title: "/etf" }, { title: "/etf/etf-providers" }, @@ -29,10 +29,10 @@ const pages = [ { title: "/list/market-cap/small-cap-stocks" }, { title: "/list/market-cap/micro-cap-stocks" }, { title: "/list/market-cap/nano-cap-stocks" }, - { title: "/list/highest-open-interest" }, - { title: "/list/highest-open-interest-change" }, - { title: "/list/highest-option-iv-rank" }, - { title: "/list/highest-option-premium" }, + { title: "/list/highest-open-interest" }, + { title: "/list/highest-open-interest-change" }, + { title: "/list/highest-option-iv-rank" }, + { title: "/list/highest-option-premium" }, { title: "/list/bitcoin-etfs" }, { title: "/stock-screener" }, { title: "/market-news" }, @@ -66,7 +66,7 @@ const pages = [ { title: "/analysts" }, { title: "/analysts/top-stocks" }, { title: "/heatmap" }, - { title: "/market-flow" }, + { title: "/market-flow" }, ]; const website = "https://stocknear.com"; @@ -76,8 +76,6 @@ export async function GET({ locals }) { //get all posts; const { apiKey, apiURL, pb } = locals; - - const rawData = await fetch(apiURL + "/full-searchbar", { method: "GET", headers: { @@ -92,16 +90,15 @@ export async function GET({ locals }) { type: item?.type, })); + const articles = await pb.collection("articles").getFullList({ + sort: "-created", + }); - const articles = await pb.collection("articles").getFullList({ - sort: "-created", - }); - - const tutorials = await pb.collection("tutorials").getFullList({ - sort: "-created", - }); + const tutorials = await pb.collection("tutorials").getFullList({ + sort: "-created", + }); - const body = sitemap(stocks, articles, pages, tutorials); + const body = sitemap(stocks, articles, pages); const response = new Response(body); response.headers.set("Cache-Control", "max-age=0, s-maxage=3600"); response.headers.set("Content-Type", "application/xml"); @@ -113,7 +110,6 @@ const sitemap = ( stocks, articles, pages, - tutorials, ) => ` ` + .map( + (item) => ` ${website}/blog/article/${convertToSlug(item?.title)} `, - ) - .join("")} + ) + .join("")} ${tutorials - .map( - (item) => ` + .map( + (item) => ` ${website}/learning-center/article/${convertToSlug(item?.title)} `, - ) - .join("")} + ) + .join("")} ${stocks .map((ticker) => { // Determine the path based on the type of the ticker @@ -158,7 +154,7 @@ const sitemap = ( ? "/stocks/" : ticker.type === "ETF" ? "/etf/" - : "/index/"; + : "/crypto/"; return ` ${website}${path}${ticker.id}