a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add image hashes as fallback

+97 -48
+1 -1
components/ThemeManager/ThemeProvider.tsx
··· 57 57 className="pageWrapper w-full bg-bg-page text-primary h-screen flex flex-col bg-cover bg-center bg-no-repeat items-stretch" 58 58 style={ 59 59 { 60 - backgroundImage: `url(${backgroundImage?.data.src})`, 60 + backgroundImage: `url(${backgroundImage?.data.src}), url(${backgroundImage?.data.fallback})`, 61 61 backgroundRepeat: backgroundImageRepeat ? "repeat" : "no-repeat", 62 62 backgroundSize: !backgroundImageRepeat 63 63 ? "cover"
+50 -44
package-lock.json
··· 19 19 "base64-js": "^1.5.1", 20 20 "drizzle-orm": "^0.30.10", 21 21 "fractional-indexing": "^3.2.0", 22 - "next": "^14.2.3", 22 + "next": "^14.2.4", 23 23 "postgres": "^3.4.4", 24 24 "prosemirror-commands": "^1.5.2", 25 25 "prosemirror-keymap": "^1.2.2", ··· 32 32 "react-use-measure": "^2.1.1", 33 33 "replicache": "^14.2.2", 34 34 "replicache-react": "^5.0.1", 35 + "thumbhash": "^0.1.1", 35 36 "y-prosemirror": "^1.2.5", 36 37 "yjs": "^13.6.15", 37 38 "zustand": "^4.5.2" ··· 1409 1410 } 1410 1411 }, 1411 1412 "node_modules/@next/env": { 1412 - "version": "14.2.3", 1413 - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", 1414 - "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" 1413 + "version": "14.2.4", 1414 + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.4.tgz", 1415 + "integrity": "sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==" 1415 1416 }, 1416 1417 "node_modules/@next/eslint-plugin-next": { 1417 1418 "version": "14.2.3", ··· 1469 1470 } 1470 1471 }, 1471 1472 "node_modules/@next/swc-darwin-arm64": { 1472 - "version": "14.2.3", 1473 - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", 1474 - "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", 1473 + "version": "14.2.4", 1474 + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.4.tgz", 1475 + "integrity": "sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==", 1475 1476 "cpu": [ 1476 1477 "arm64" 1477 1478 ], ··· 1484 1485 } 1485 1486 }, 1486 1487 "node_modules/@next/swc-darwin-x64": { 1487 - "version": "14.2.3", 1488 - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", 1489 - "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", 1488 + "version": "14.2.4", 1489 + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz", 1490 + "integrity": "sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==", 1490 1491 "cpu": [ 1491 1492 "x64" 1492 1493 ], ··· 1499 1500 } 1500 1501 }, 1501 1502 "node_modules/@next/swc-linux-arm64-gnu": { 1502 - "version": "14.2.3", 1503 - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", 1504 - "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", 1503 + "version": "14.2.4", 1504 + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz", 1505 + "integrity": "sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==", 1505 1506 "cpu": [ 1506 1507 "arm64" 1507 1508 ], ··· 1514 1515 } 1515 1516 }, 1516 1517 "node_modules/@next/swc-linux-arm64-musl": { 1517 - "version": "14.2.3", 1518 - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", 1519 - "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", 1518 + "version": "14.2.4", 1519 + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz", 1520 + "integrity": "sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==", 1520 1521 "cpu": [ 1521 1522 "arm64" 1522 1523 ], ··· 1529 1530 } 1530 1531 }, 1531 1532 "node_modules/@next/swc-linux-x64-gnu": { 1532 - "version": "14.2.3", 1533 - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", 1534 - "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", 1533 + "version": "14.2.4", 1534 + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz", 1535 + "integrity": "sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==", 1535 1536 "cpu": [ 1536 1537 "x64" 1537 1538 ], ··· 1544 1545 } 1545 1546 }, 1546 1547 "node_modules/@next/swc-linux-x64-musl": { 1547 - "version": "14.2.3", 1548 - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", 1549 - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", 1548 + "version": "14.2.4", 1549 + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz", 1550 + "integrity": "sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==", 1550 1551 "cpu": [ 1551 1552 "x64" 1552 1553 ], ··· 1559 1560 } 1560 1561 }, 1561 1562 "node_modules/@next/swc-win32-arm64-msvc": { 1562 - "version": "14.2.3", 1563 - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", 1564 - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", 1563 + "version": "14.2.4", 1564 + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz", 1565 + "integrity": "sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==", 1565 1566 "cpu": [ 1566 1567 "arm64" 1567 1568 ], ··· 1574 1575 } 1575 1576 }, 1576 1577 "node_modules/@next/swc-win32-ia32-msvc": { 1577 - "version": "14.2.3", 1578 - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", 1579 - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", 1578 + "version": "14.2.4", 1579 + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz", 1580 + "integrity": "sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==", 1580 1581 "cpu": [ 1581 1582 "ia32" 1582 1583 ], ··· 1589 1590 } 1590 1591 }, 1591 1592 "node_modules/@next/swc-win32-x64-msvc": { 1592 - "version": "14.2.3", 1593 - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", 1594 - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", 1593 + "version": "14.2.4", 1594 + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz", 1595 + "integrity": "sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==", 1595 1596 "cpu": [ 1596 1597 "x64" 1597 1598 ], ··· 8498 8499 "dev": true 8499 8500 }, 8500 8501 "node_modules/next": { 8501 - "version": "14.2.3", 8502 - "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", 8503 - "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", 8502 + "version": "14.2.4", 8503 + "resolved": "https://registry.npmjs.org/next/-/next-14.2.4.tgz", 8504 + "integrity": "sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==", 8504 8505 "dependencies": { 8505 - "@next/env": "14.2.3", 8506 + "@next/env": "14.2.4", 8506 8507 "@swc/helpers": "0.5.5", 8507 8508 "busboy": "1.6.0", 8508 8509 "caniuse-lite": "^1.0.30001579", ··· 8517 8518 "node": ">=18.17.0" 8518 8519 }, 8519 8520 "optionalDependencies": { 8520 - "@next/swc-darwin-arm64": "14.2.3", 8521 - "@next/swc-darwin-x64": "14.2.3", 8522 - "@next/swc-linux-arm64-gnu": "14.2.3", 8523 - "@next/swc-linux-arm64-musl": "14.2.3", 8524 - "@next/swc-linux-x64-gnu": "14.2.3", 8525 - "@next/swc-linux-x64-musl": "14.2.3", 8526 - "@next/swc-win32-arm64-msvc": "14.2.3", 8527 - "@next/swc-win32-ia32-msvc": "14.2.3", 8528 - "@next/swc-win32-x64-msvc": "14.2.3" 8521 + "@next/swc-darwin-arm64": "14.2.4", 8522 + "@next/swc-darwin-x64": "14.2.4", 8523 + "@next/swc-linux-arm64-gnu": "14.2.4", 8524 + "@next/swc-linux-arm64-musl": "14.2.4", 8525 + "@next/swc-linux-x64-gnu": "14.2.4", 8526 + "@next/swc-linux-x64-musl": "14.2.4", 8527 + "@next/swc-win32-arm64-msvc": "14.2.4", 8528 + "@next/swc-win32-ia32-msvc": "14.2.4", 8529 + "@next/swc-win32-x64-msvc": "14.2.4" 8529 8530 }, 8530 8531 "peerDependencies": { 8531 8532 "@opentelemetry/api": "^1.1.0", ··· 10371 10372 "engines": { 10372 10373 "node": ">=0.8" 10373 10374 } 10375 + }, 10376 + "node_modules/thumbhash": { 10377 + "version": "0.1.1", 10378 + "resolved": "https://registry.npmjs.org/thumbhash/-/thumbhash-0.1.1.tgz", 10379 + "integrity": "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg==" 10374 10380 }, 10375 10381 "node_modules/timers-ext": { 10376 10382 "version": "0.1.7",
+2 -1
package.json
··· 21 21 "base64-js": "^1.5.1", 22 22 "drizzle-orm": "^0.30.10", 23 23 "fractional-indexing": "^3.2.0", 24 - "next": "^14.2.3", 24 + "next": "^14.2.4", 25 25 "postgres": "^3.4.4", 26 26 "prosemirror-commands": "^1.5.2", 27 27 "prosemirror-keymap": "^1.2.2", ··· 34 34 "react-use-measure": "^2.1.1", 35 35 "replicache": "^14.2.2", 36 36 "replicache-react": "^5.0.1", 37 + "thumbhash": "^0.1.1", 37 38 "y-prosemirror": "^1.2.5", 38 39 "yjs": "^13.6.15", 39 40 "zustand": "^4.5.2"
+1
src/replicache/attributes.ts
··· 78 78 }; 79 79 image: { 80 80 type: "image"; 81 + fallback: string; 81 82 src: string; 82 83 height: number; 83 84 width: number;
+43 -2
src/utils/addImage.ts
··· 2 2 import { ReplicacheMutators } from "../replicache"; 3 3 import { supabaseBrowserClient } from "supabase/browserClient"; 4 4 import { FilterAttributes } from "src/replicache/attributes"; 5 + import { rgbaToDataURL, rgbaToThumbHash, thumbHashToDataURL } from "thumbhash"; 5 6 6 7 export async function addImage( 7 8 file: File, ··· 14 15 let client = supabaseBrowserClient(); 15 16 let cache = await caches.open("minilink-user-assets"); 16 17 let fileID = crypto.randomUUID(); 17 - let url = client.storage.from("minilink-user-assets").getPublicUrl(fileID) 18 - .data.publicUrl; 18 + let url = client.storage.from("minilink-user-assets").getPublicUrl(fileID, { 19 + transform: { 20 + quality: 80, 21 + }, 22 + }).data.publicUrl; 19 23 let dimensions = await getImageDimensions(file); 20 24 await cache.put( 21 25 url, ··· 29 33 //This may reach other clients before the image has been uploaded. 30 34 // Maybe we should set the state to uploaded-by (client_ID) or something 31 35 // and then set the real one after. 36 + let thumbhash = await getThumbHash(file); 32 37 await rep.mutate.assertFact({ 33 38 entity: args.entityID, 34 39 attribute: "block/image", 35 40 data: { 41 + fallback: thumbhash, 36 42 type: "image", 37 43 local: rep.clientID, 38 44 src: url, ··· 45 51 entity: args.entityID, 46 52 attribute: args.attribute, 47 53 data: { 54 + fallback: thumbhash, 48 55 type: "image", 49 56 src: url, 50 57 height: dimensions.height, 51 58 width: dimensions.width, 52 59 }, 53 60 }); 61 + } 62 + 63 + async function getThumbHash(file: File) { 64 + const arrayBuffer = await file.arrayBuffer(); 65 + const blob = new Blob([arrayBuffer], { type: file.type }); 66 + const imageBitmap = await createImageBitmap(blob); 67 + 68 + const canvas = document.createElement("canvas"); 69 + const context = canvas.getContext("2d") as CanvasRenderingContext2D; 70 + const maxDimension = 100; 71 + let width = imageBitmap.width; 72 + let height = imageBitmap.height; 73 + 74 + if (width > height) { 75 + if (width > maxDimension) { 76 + height *= maxDimension / width; 77 + width = maxDimension; 78 + } 79 + } else { 80 + if (height > maxDimension) { 81 + width *= maxDimension / height; 82 + height = maxDimension; 83 + } 84 + } 85 + 86 + canvas.width = width; 87 + canvas.height = height; 88 + context.drawImage(imageBitmap, 0, 0, width, height); 89 + 90 + const imageData = context.getImageData(0, 0, width, height); 91 + const thumbHash = thumbHashToDataURL( 92 + rgbaToThumbHash(imageData.width, imageData.height, imageData.data), 93 + ); 94 + return thumbHash; 54 95 } 55 96 56 97 function getImageDimensions(