今日已更新 257 条资讯 | 累计 40702 条内容
关于我们

标签:#JavaScript

找到 1080 篇相关文章

AI 资讯

Next step to client-side storage

Next step to client-side storage In my past one blog, I wrote about how I improve the performance of the application using the local storage. And the problem local storage solves. But now I face another problem about the client storage. My project is simply about order management software for the rental clothing industry. In the rental clothing industry, Showrooms or small shops have a big problem. The problem starts when one order has a single or multiple items that are booked in a particular time range. Now, a second order wants the same item in between that particular time range. If, by mistake, the second order books that item, then the problem starts. The item is booked two times in that particular time range. That is called double booking of the item. This mistake is created by the use of traditional register booking. Now, when I need to store the items data, that is a small amount of data, so I simply use the local storage. But now I need another and a big storage for storing order details. I build two features: first one is for showing all the orders and second one is for showing the full order. To implement those features and to maintain the user experience, I decide to store a small amount of data about the order on the client side. First, I decide to store data in local storage. But to store data in the local storage is not a good option because the local storage is used for storing small details about the application, and storing order details in the local storage compromises the performance of the application. Now I want a new storage option for storing order details. And again I find out, and that is the IndexedDB. To integrate IndexedDB in my application, I want to learn about that storage. I search multiple videos about IndexedDB, but no one is teaching me properly. After finding hundreds of tutorials, I finally found one tutorial that is teaching properly how to integrate IndexedDB in the application. Now I want to share that learning with you. To i

2026-08-23 原文 →
AI 资讯

I built a JSON toolkit that never sends your data anywhere

Most "paste your JSON here" tools online send that JSON to a server to process it. For internal API responses, config files, or anything with real data in it, that's not something I wanted to do — so I built JSONLinter , a JSON toolkit where literally everything happens client-side. What it does It started as a validator/formatter, then grew into 42 tools across six categories: Validate & Format — validation with precise error locations, pretty print, minify, JSON repair (fixes trailing commas, single quotes, unquoted keys, truncated JSON, etc.) View & Query — tree view, JSONPath queries, structural diff (key order doesn't matter), full-text search Data Converters — CSV, Excel, YAML, XML, SQL, Markdown, both directions Code Generators — infers a type model from a JSON sample and generates TypeScript, Python, Java, C#, Go, Kotlin, Swift, Rust, or PHP Schema Tools — JSON Schema validation + generation Encoding Tools — Base64, escape/unescape, JWT decode There's also an optional AI assistant on the validator page — it's bring-your-own-key (OpenAI or Anthropic), and since there's no backend at all, the key and your JSON go straight from your browser to the provider. I never see either. How it's built Stack is React 19 + TypeScript + Vite, Tailwind v4 for styling, CodeMirror 6 for the editor. A few things I had to solve that were more interesting than expected: Prerendering without SSR. I didn't want to take on a Next.js-style server just to get real HTML for crawlers. Instead, the build runs a headless Chromium pass (Playwright) over every route after vite build and saves the fully-rendered output to dist/<route>/index.html . Crawlers get real content and correct per-page meta tags on first paint; once JS loads, React takes over exactly like a normal SPA. No server, no hydration mismatches to worry about. Structural JSON diff. A text diff on two JSON documents is mostly useless because key order doesn't matter semantically. The diff tool parses both sides and compares t

2026-08-23 原文 →
开发者

Cómo solucionar el error \"Text content does not match server-rendered HTML\" en Next.js App Router

Cómo solucionar el error "Text content does not match server-rendered HTML" en Next.js App Router Este error ocurre cuando el HTML generado en el servidor (SSR/SSG) no coincide con el árbol de React que se construye durante la primera renderización en el navegador (hydration). Es un problema crítico de consistencia de estado que rompe la experiencia de usuario y puede causar comportamientos impredecibles. 🔍 Causa raíz (diagnóstico técnico) En tu caso, el error está relacionado con contenido dinámico que varía entre renderizado del servidor y renderizado del cliente , probablemente causado por: Uso de Date() , Math.random() , localStorage , window , o APIs del navegador directamente en el render . Uso de typeof window !== 'undefined' como condición de renderizado (no es idempotente entre SSR y CSR). Metaetiquetas de detección automática de iOS ( format-detection ) que inyectan nodos <a> en tiempo de ejecución. Extensiones del navegador (especialmente en desarrollo) que modifican el DOM. Librerías CSS-in-JS mal configuradas que inyectan clases o estilos dinámicos en CSR. ⚠️ Nota crítica : Next.js App Router no permite el uso de useEffect para evitar el mismatch en el primer render — el mismatch debe prevenirse , no suprimirse . ✅ Solución definitiva (pasos verificados) Paso 1: Elimina toda lógica no determinista del render NUNCA uses lo siguiente directamente en el cuerpo del componente: // ❌ Evitar const now = new Date (); // ❌ const isClient = typeof window !== ' undefined ' ; // ❌ const randomId = Math . random (); // ❌ const theme = localStorage . getItem ( ' theme ' ); // ❌ ✅ Reemplaza con: // ✅ Usar `useEffect` para *actualizar* el estado, no para *determinar* el render inicial import { useState , useEffect } from ' react ' ; export default function Component () { const [ time , setTime ] = useState < string > ( '' ); // Inicializa con valor seguro (ej. string vacío o placeholder) useEffect (() => { setTime ( new Date (). toISOString ()); }, []); return < time d

2026-08-23 原文 →
AI 资讯

Building a Live, User-Controlled Canvas Background System That Doesn't Kill Low-End Phones

The idea Most apps give you a static background. I wanted Pairly to feel alive instead, so I built "Atmosphere": a real-time animated Canvas layer that sits behind every chat, fully tunable by the user, speed, density, opacity, brightness, saturation, all live. There are currently over 40 atmospheres in the system, from calm ones like Snow and Fireflies to more elaborate ones like a black hole accretion disk called Abyss. The interesting part wasn't drawing pretty particles. It was making that work smoothly on a five-year-old Android phone without draining the battery in ten minutes. Two rendering paths, not one Atmosphere isn't a single renderer, it's a small internal package ( @pairly/atmospheres ) with two shared engines that every individual atmosphere builds on: ParticleCanvas , a generic particle system for anything made of many independent objects: snow, fireflies, sakura petals. useCanvasLoop , a raw draw-loop hook for continuous scenes that aren't particle-based, like Abyss's swirling accretion disk. Both engines centralize every "don't destroy the device" concern in one place, so individual atmospheres never have to think about it. Here's useCanvasLoop 's frame loop: const frameInterval = 1000 / perf . fps ; let raf = 0 ; let last = performance . now (); let acc = 0 ; const loop = ( now : number ) => { if ( ! running ) return ; raf = requestAnimationFrame ( loop ); const elapsed = now - last ; last = now ; acc += elapsed ; if ( acc < frameInterval ) return ; const dt = acc / 1000 ; acc = 0 ; draw ( ctx , width , height , elapsedTime , perf ); }; requestAnimationFrame fires at the display's native rate (often 90-120Hz on phones now), but that doesn't mean you should draw every single time it fires. This accumulator pattern throttles actual drawing down to the target FPS from the device's performance profile, instead of trusting rAF's raw rate. Profiling the device before drawing anything Before any atmosphere renders a single frame, it checks the device: ex

2026-08-23 原文 →
AI 资讯

I wrote the privacy rule, enforced it, commented it, and shipped the leak anyway

This is a submission for DEV's Summer Bug Smash : Smash Stories. TL;DR. I wrote a scrubbing policy before writing any instrumentation code. I enforced it in a beforeSend hook. I unit tested it. I wrote a comment above the one obviously sensitive line saying exactly what it must never do. Then I intercepted the actual bytes leaving the browser and found a stranger's shoulder injury in them. Every guarantee I had written was about data my code hands to the SDK. None of them were about data the SDK collects on its own. The setup WhyRep is a workout tracker built local-first. Training data is created and read on the device, the tracker works offline with no account, and that is not a marketing line, it is the architecture. It is also the thing people decide to trust or not trust in about four seconds on the landing page. So when I added Sentry, the scrubbing policy came before the code. Written down, in the repo, as a list of things that may never appear in an event: exercise names, weights, reps, RIR, session notes, chat content. Never. On Android I enforced it twice. A beforeSend hook that strips the forbidden fields, and a unit test that constructs an event carrying each one and asserts it comes out stripped. @Test fun `beforeSend strips every field the policy forbids` () { val event = SentryEvent (). apply { setExtra ( "exerciseName" , "Incline Barbell Bench" ) setExtra ( "weightKg" , 82.5 ) setExtra ( "notes" , "left shoulder clicks past parallel" ) } val scrubbed = ScrubbingPolicy . scrub ( event , Hint ()) assertNull ( scrubbed ?. getExtra ( "exerciseName" )) assertNull ( scrubbed ?. getExtra ( "weightKg" )) assertNull ( scrubbed ?. getExtra ( "notes" )) } Green. Good. Then I wired up the landing site's share-link page. It decodes whyrep.com/t#<payload> , where the payload is somebody's entire workout template, base64 in the URL fragment. I was careful there too. On a decode failure it reports a coarse reason tag and never the payload: // NEVER send the payload i

2026-08-23 原文 →
AI 资讯

Someone forked my React component instead of opening an issue

I maintain a small comic and manga viewer component for React called react-comic-viewer . The other day I was poking around npm and noticed something odd — there were three other packages with basically my package's name, published by other people. All three were forks of mine. Same description, same repository URL pointing back at my repo. None of the three authors had ever opened an issue or a pull request on my side. What the fork changed The oldest fork was made about three months after I first published, and it kept going for almost a year. Its version number ran ahead of mine at the time — I was on 0.3.5 while the fork was on 0.6.3. So I read the diff. Honestly, it was more useful than any issue would have been. The commit messages alone told the whole story: remove sass fix: support className props use Hotkeys and a new example file called controlled.tsx The sass one I'd already fixed. The className one I'd fixed too, about a year later. The controlled.tsx one I had never fixed. Not in four years. The part I never fixed Here's what their example looked like: < ComicViewer currentPage = { currentPage } isExpansion = { false } onTryMoveNextPage = { ( nextPage ) => { /* ... */ } } onChangedCurrentPage = { ( page ) => setCurrentPage ( page ) } pages = { pages } /> And here's what my component actually accepted: < ComicViewer initialCurrentPage = { 0 } initialIsExpansion = { false } onChangeCurrentPage = { ( page ) => { /* ... */ } } pages = { pages } /> The initial prefix is the whole problem. My component would take a starting page from you, and then never let you touch it again. It owned that state for the rest of its life. That's fine for a demo. It's pretty bad for anything real. You can't jump to a page from a table of contents. Syncing the current page with the URL doesn't work either. And if a chapter needs to be purchased first, there's no way to step in and stop the move. Every one of those needs the parent to be in charge, and the parent never was. Maki

2026-08-23 原文 →
开发者

Forms in React : From Inputs to Controlled Components

You have probably written HTML forms before, and so the structure below resonates with you. Perhaps you even smile because, this one, you understand. <form> <input type= "text" /> <button> Submit </button> </form> If you have done this, you know what happens when you click the button. The whole form reloads, the changes or inputs are cleared. This is the default behavior of forms in HTML. In React, we handle every step and every stage so that we have control over the data and the behavior of the form and data. The above signature represents what we call UNCONTROLLED INPUT . This means that there isn't a single source of truth to the value of this field, hence it can change to anything, and any value In addition to the above attributes, we will add value and onChange props to the input element as below: <input type= "text" value= {} onChange= {}/ > value represents the content of the input field e.g. the name text that the user enters in a Name field. onChange is the function that will be triggered everytime the input changes. Whenever a key is pressed within this field, this function will be invoked. Controlled inputs have their values set and manipulated by states, as we saw in Part 1 of the series. Uncontrolled inputs on the other hand do not have a manager that will dictate what goes into the field and when. Now let's write our first React Input, we'll keep it simple. import { useState } from " react " function Form (){ const [ name , setName ] = useState ( "" ) return ( < input type = " text " value = { name } onChange = {( event ) => setName ( event . target . value )} / > ) Let's look at what happens in the above. We have declared a state [name,setName] . name is the state variable setName is a function used to update the variable We then initialized an input element with properties value and onChange Note that, when the value of an input is set, that will always be the value even if you type something into the box. That is the essence of controlled input. The

2026-08-23 原文 →
开发者

Hello Everyone

Hello everyone, I am new to coding just begun to learn the ins and outs of coding and what it can do. I am in the process of getting my Full Stack Developer certificates. I have always wanted to do something that has to do with computers because I needed something to pass the time when I hurt myself playing football. I am looking forward to chatting with all of you about the struggles you had and what you found that you liked within the development realm.

2026-08-23 原文 →
开发者

Your birth time is lying to you: a time-zone rabbit hole in a Chinese astrology calculator

I built a calculator for BaZi — Chinese "Four Pillars" birth charts. Whatever you think of the interpretive tradition (and I'll get to that), the input math turned out to be a genuinely deep time-zone problem, and that's what this post is about. If you've ever thought "time zones, how hard can it be" — this is a tour of exactly how hard, with working TypeScript. The problem BaZi divides the day into twelve two-hour "branches", so your birth hour is one of the chart's four pillars. Get the hour wrong and you get a different chart — not slightly different, categorically different. Every calculator I could find feeds the system the wall-clock time from your birth certificate. But the tradition predates time zones by about two thousand years; it obviously means solar time — where the sun actually was over your birthplace. Clock time and solar time differ by more than most people think, and the difference decomposes into exactly three parts: 1. Daylight saving time — and it's historical. You need the DST rules in force on the birth date , not today's. China ran a now-forgotten DST experiment from 1986–91; Harbin kept its own zone before 1949. If you were born in Beijing in July 1988, your certificate is an hour ahead of standard time and no modern-day lookup will tell you that. 2. Longitude. Solar time shifts 4 minutes per degree from your zone's standard meridian. China spans five geographic zones but uses one clock — born in Ürümqi, your clock runs about two hours ahead of the sun. It's not just a China quirk: Vancouver sits at 123°W in a zone whose meridian is 120°W, so that's another 12 minutes, everywhere, always. 3. The equation of time. The sun itself runs up to ±16 minutes fast or slow over the year, thanks to orbital eccentricity and axial tilt. NOAA publishes an approximation that's accurate to under a minute: /** Equation of time (minutes), NOAA approximation */ export function equationOfTimeMinutes ( dayOfYear : number ): number { const b = ( 2 * Math . PI *

2026-08-23 原文 →
AI 资讯

The multilingual bugs that never throw: hreflang, JSON-LD and a site in 12 languages

I run a search engine that publishes in twelve languages from one static site on Cloudflare Pages. Last week I audited its machine-readable layer — the part crawlers and answer engines read rather than humans — and found four problems. None of them threw an error. None appeared in logs. Every page rendered perfectly. That is the whole point of this post: the multilingual layer fails in a register where nothing tells you. 1. The homepage was serving the wrong language to everyone abroad The site's primary market speaks Hebrew, so / is Hebrew and /en/ , /ar/ , /de/ and nine others sit alongside it. A middleware rule redirected visitors from one specific region to their language. Everyone else — including every English speaker on earth — landed on Hebrew. My first instinct was to fix it with a broader geo-redirect: detect English-speaking countries, send them to /en/ . This would have been a bad idea, and it is worth saying why. Googlebot crawls predominantly from US IPs. A geo-redirect on / that keys off country would take the crawler off the Hebrew homepage and onto the English one almost every time it visited. You do not want your primary-market homepage to become the page the crawler can never reach. The correct tool is hreflang , and it is what search engines built for exactly this. Checking the page, the tags were already there and already right: <link rel= "alternate" hreflang= "he" href= "https://example.com/" > <link rel= "alternate" hreflang= "en" href= "https://example.com/en/" > <link rel= "alternate" hreflang= "ar" href= "https://example.com/ar/" > <!-- …ten more… --> <link rel= "alternate" hreflang= "x-default" href= "https://example.com/en/" > Two things make this work, and both are easy to get wrong: The set must be reciprocal. Every page in the group lists every other page including itself . If /en/ does not point back at / , search engines are entitled to ignore the whole cluster. x-default is not "the default language" — it is the fallback for users

2026-08-22 原文 →
开源项目

Four things SVG and CSS did that I did not expect

I spent a while building an icon editor that runs entirely in the browser (icons.jamuny.com, free, no account). Here is what cost me the most time. A presentation attribute loses to any author CSS rule I was scaling handle stroke widths by 1 / zoom and writing the result as an attribute. The value was never used. handle . setAttribute ( ' stroke-width ' , String ( 0.35 / zoom )); .handle { stroke-width: 0.35 } in the stylesheet outranks it, because a presentation attribute sits at the very bottom of the cascade. Measured in Chromium: an attribute of 0.05 computed as 0.35px . Every handle thickened on screen as you zoomed in, for months, with no error anywhere. The fix is a custom property, which is an ordinary declaration and wins where an attribute cannot: layer . style . setProperty ( ' --px ' , String ( 1 / zoom )); /* .handle { stroke-width: calc(0.35 * var(--px)) } */ Geometry attributes like r and width are unaffected. They have no CSS counterpart here, so nothing was ever overriding them. A focused SVG element gets a focus ring measured in user units My canvas is 24 units wide and about 620 pixels. Chrome drew its default focus ring at outline-width: 2.72727px in user units, which is about 24 screen pixels. A fat blue disc appeared around every point you clicked. It was reported to me four times, and four times I thinned something of my own that was not the cause. getComputedStyle ( document . activeElement ). outline That one line found it. My rule only covered :focus-visible , which is the keyboard case, and the keyboard case is the one where I draw a ring of my own. var() does work in a presentation attribute, and I wrote down that it doesn't I needed a segment colour that changes with the theme, so the value is oklch(var(--band-l) var(--band-c) 47) . I applied it through a style and put a comment beside it saying var() is not substituted in presentation attributes. It is. Both forms compute to the same colour, including on an element built detached and ap

2026-08-22 原文 →
AI 资讯

I built an OLX scraper for 24 countries — the boring version that actually ships

I built an OLX scraper for 24 countries — the boring version that actually ships OLX runs classifieds in about two dozen countries. Same brand, different domains, different anti-bot setups. Everyone scraping it does one country at a time. I got tired of forking. So I put 24 countries behind one input. country: "id" or country: "pl" or country: "br" — same schema out. It's live on Apify as primesieve/olx-global-scraper . One file. No browser. Here is the boring part that matters. What it does Input: { "country" : "id" , "keywords" : [ "iphone 13" ], "maxResults" : 50 , "maxPages" : 3 , "proxyConfiguration" : { "useApifyProxy" : true , "apifyProxyGroups" : [ "RESIDENTIAL" ] } } country — two-letter code ( id , pl , in , br , ua , pt , ro , bg , kz , uz , pk , za , ng , ke , eg , lb , ph , co , ar , pe , ec , gt , az , ma ). Default id . keywords — one or more search terms. Each runs sequentially. maxResults / maxPages — caps. Defaults 50 / 3, max 1000 / 30. proxyConfiguration — optional for Indonesia, required for the other 23. Output — same shape every country: { "listingId" : "123456789" , "title" : "iPhone 13 128GB mulus" , "price" : 6500000 , "priceText" : "Rp 6.500.000" , "currency" : "IDR" , "city" : "Jakarta Selatan" , "location" : "Tebet, Jakarta Selatan, DKI Jakarta" , "images" : [ "https://...jpg" ], "thumbnailUrl" : "https://...jpg" , "listingUrl" : "https://www.olx.co.id/item/123456789" , "country" : "id" } Title, price (numeric plus display text), currency, location, images, URL. No seller PII beyond what the listing page shows. No tricks. Try: https://apify.com/primesieve/olx-global-scraper The boring stack // no playwright, no puppeteer // apify + fetch + cheerio. That's it. The scraper is one file. Apify SDK for input, dataset, and pay-per-event. Native fetch for HTTP. cheerio for the HTML path. Undici ProxyAgent when a proxy is configured. Node 20, 512 MB, 600s timeout. I check the endpoint before I write the scraper. Indonesia answered with clean JSO

2026-08-22 原文 →
AI 资讯

Why free chess analysis is always capped at one game a day

Most free chess game review gives you one game per day. Chess.com works that way, and so does almost every smaller site offering the feature. I assumed for a long time that this was just a paywall placed where it hurts. It is partly that. But there is a real cost sitting behind the cap, and once I worked out what the cost was, I built my own analysis site differently. The cost of one game review Reviewing a 40 move game means evaluating about 80 positions. Give the engine two seconds on each one and you have spent close to three minutes of CPU. None of it is cacheable, because your game is not anyone else's game. Run that on your own hardware and you pay for every minute. A thousand people reviewing one game a day is roughly 50 CPU hours daily, for a feature you are giving away. The quota is not greed. It is the number that stops the free tier from eating the company. Which raises a more interesting question than "how do I price this". What happens if you delete the cost instead of rationing it? Move the engine to the client Stockfish compiles to WebAssembly. Put it in a Web Worker and the visitor's own processor spends those three minutes. Your server ships static files and never sees a chess position. The whole free tier problem disappears, because there is no per-user cost left to control. Nothing to meter, so nothing to cap. Getting started is unremarkable: const engine = new Worker ( " stockfish.js " ); engine . postMessage ( " uci " ); engine . postMessage ( " isready " ); After that you speak UCI over postMessage . Set a position, ask the engine to think, and read results off the message stream: engine . postMessage ( `position fen ${ fen } ` ); engine . postMessage ( `go depth 15 movetime 2000` ); That is the pitch. Now the parts nobody mentions. The protocol is strings, and it is asynchronous UCI was designed for a pipe between two processes. You get that pipe, faithfully, with all of its ergonomics intact. The engine answers with lines like this: info dept

2026-08-22 原文 →
AI 资讯

Cómo solucionar el error “Enable JavaScript and cookies to continue”

Cómo solucionar el error “Enable JavaScript and cookies to continue” Este mensaje aparece cuando Cloudflare (u otro proxy de seguridad similar) bloquea la solicitud porque detecta que el cliente no cumple con los requisitos mínimos de seguridad: JavaScript deshabilitado o cookies deshabilitadas/expiradas . 🔍 Causa técnica Cloudflare implementa mecanismos de protección como: JavaScript Challenge : El navegador debe ejecutar un script para demostrar que no es un bot. Cookie de verificación : Tras superar el desafío, Cloudflare emite una cookie ( __cf_bm o cf_clearance ) que valida la sesión. Si el cliente (navegador o cliente HTTP personalizado) no ejecuta JavaScript o no maneja cookies correctamente, la validación falla y se muestra este mensaje. ✅ Solución definitiva (por escenario) 🌐 Si eres un usuario final (navegador) Habilita JavaScript : Chrome: Configuración > Privacidad y seguridad > Sitios web no seguros > Habilitar JavaScript . Firefox: Preferencias > Privacidad y seguridad > Permisos > Habilitar JavaScript . Habilita cookies de terceros (si usas extensiones como uBlock Origin o Privacy Badger): Añade el dominio a la lista blanca. Desactiva temporalmente los bloqueadores para probar. Borra cookies y caché del dominio afectado. Reinicia el navegador y vuelve a cargar la página. 🧪 Si eres desarrollador (automatización / scraping / cliente HTTP) ❌ No uses requests o curl sin soporte JS/cookies → fallarán siempre . ✅ Opción recomendada: Usa un navegador headless con soporte JS y cookies # Ejemplo con Playwright (recomendado) from playwright.sync_api import sync_playwright with sync_playwright () as p : browser = p . chromium . launch ( headless = True ) context = browser . new_context () page = context . new_page () # Navega a la URL (Cloudflare se resolverá automáticamente) page . goto ( " https://ejemplo.com " , wait_until = " networkidle " ) # Si aún falla, fuerza espera tras el desafío try : page . wait_for_selector ( " #challenge-error-text " , timeout = 5

2026-08-22 原文 →
AI 资讯

VoidZero Releases Vite+ Beta: A Unified Web Toolchain Behind a Single Command

VoidZero has launched the beta of Vite+, a unified web development toolchain. It combines runtime, package management, and essential frontend tools under a single command. Vite+ supports various projects and is open source. The platform enhances workflow through features such as hot-reloading, format checking, and testing. The team emphasizes community feedback for future updates. By Daniel Curtis

2026-08-22 原文 →
AI 资讯

I Built Browser-Local File Tools So Files Don't Need to Be Uploaded

A lot of small file jobs still follow the same awkward pattern: choose a file, upload it to someone else's server, wait for processing, download the result. For some tasks, that server round trip is unnecessary. I have been building FileNest Worktools , a browser-based toolkit for repetitive file work, around a simple constraint: If a task can reasonably be done inside the browser, the file contents should stay on the user's device. What currently runs locally The current FileNest tools cover: batch file renaming sequential, reverse, and custom-order renaming JPG / PNG / WebP conversion image resizing and compression image-to-text OCR with editable review PDF merge, split, extract, and images-to-PDF text export to DOCX, PDF, TXT, Markdown, and HTML CSV / TSV / JSON conversion duplicate-file detection For these current browser-local workflows, the file contents are processed on the device rather than being sent to a conversion server. Why local processing matters Privacy is one reason, but it is not the only one. Many file operations do not actually need server-side infrastructure. Renaming a file is mostly about filenames, order, extensions, and conflict checks. Image resizing can be handled with browser APIs. CSV and JSON conversion is essentially local parsing and serialization. Duplicate detection can compare file fingerprints locally. If those jobs can stay inside the browser, there is less network overhead and one less copy of the user's files being created somewhere else. I also wanted the risky parts to stay visible One thing I dislike about many online file tools is the "click and hope" workflow. So I tried to make FileNest show more information before or after processing: renamed files can be previewed before packaging original files are not silently overwritten image compression reports actual output bytes duplicate detection uses content matching rather than filename guesses OCR output can be reviewed and edited the image enhancer does not claim that shar

2026-08-22 原文 →
AI 资讯

How We Handle Client-Side CSV Merging Without Server Processing

When merging CSVs in the browser, handling mismatched columns and quoted cells changes everything. Here's how filetools does it. Last week we shipped CSV merge/split/transpose tools for filetools, and the most interesting challenge wasn't CSV parsing - it was handling real-world data without a server. Here's how we handle the hard cases. The Problem: CSV files in the wild are messy. Columns don't always match. A cell value contains a comma and that comma is quoted. Headers are sometimes case-sensitive, sometimes not. When you build on a server, you can run a fast library and stream the result. In the browser, you have to make your merge operation deterministic from first load. Our approach: Column matching: Users specify which columns to merge on (e.g., "id" or "email"). We do a case-insensitive first pass, then check for exact matches. If no match exists, we warn the user and ask them to pick from the detected headers. This upfront clarity saves merge errors later. Quoted cell handling: We follow RFC 4180 strictly - a quote inside a quoted field is escaped as a double quote. Most CSV parsers get this wrong when they're quick. We use the csv-parse library (MIT) vendored into the site, same way we do with PDF and ZIP libraries. Column order: The merge operation respects column order from the first file, then appends any new columns from subsequent files. This is deterministic and reproducible. Why this matters for a browser tool: Server-based CSV tools hide their assumptions - you upload, they merge, you download. If a merge fails, you get an error message and no insight into why. Client-side, the user can see the detected headers, approve or correct them, and re-try immediately. That transparency matters when you're dealing with data that represents real records or transactions. What shipped this week: We added merge, split (by row count or column value), transpose, and comparison tools. The same deterministic, transparent approach applies to each one. Next question

2026-08-22 原文 →