renderer.tsx (8045B)
1 import React, { PropsWithChildren } from 'react'; 2 import ReactPDF, { Font, Page, Text, View, Document, StyleSheet, Link, renderToBuffer } from '@react-pdf/renderer'; 3 import Indexer from './indexer.js'; 4 import { Hits } from 'meilisearch'; 5 import prompts, { PromptObject } from 'prompts'; 6 import { fileURLToPath } from 'node:url'; 7 import { resolve } from 'node:path'; 8 9 Font.registerEmojiSource({ 10 format: 'png', 11 url: 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/', 12 }); 13 14 15 const styles = StyleSheet.create({ 16 page: { 17 fontSize: 12, 18 fontFamily: 'Helvetica', 19 flexDirection: 'column', 20 justifyContent: 'space-between', 21 backgroundColor: '#fff', 22 padding: 10, 23 } 24 }) 25 26 const indexer = new Indexer(); 27 28 const borderRadius = 2; 29 30 // A pdf renderer 31 export const MessageDocument = ({ query, room_id, sender, queryResults }: PropsWithChildren<{ query: string, room_id?: string, sender?: string, queryResults: { hits: Hits<Record<string, any>>; estimatedTotalHits: number; } }>) => { 32 return ( 33 <Document 34 title={`Search results for: "${query}"`} 35 producer='Matrix Search Engine' 36 keywords='matrix' 37 > 38 <Page size="A4" style={styles.page}> 39 {/* Title page which is centered */} 40 <View style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", height: "100%" }}> 41 <View style={{ flexDirection: "column", justifyContent: "center", alignItems: "center" }}> 42 <Text style={{ fontSize: 24, textAlign: 'center' }}>Matrix Search Engine</Text> 43 <Text style={{ fontSize: 12, textAlign: 'center', marginTop: 5 }}>Search results for: "{query}"</Text> 44 {/* Optionally display the other values of room_id and sender*/} 45 {room_id !== undefined && <Text style={{ fontSize: 12, textAlign: 'center' }}>Room ID: {room_id}</Text>} 46 {sender !== undefined && <Text style={{ fontSize: 12, textAlign: 'center' }}>Sender: {sender}</Text>} 47 {/* Number of results */} 48 <Text style={{ fontSize: 10, textAlign: 'center', marginTop: 10 }}>Number of Results: {queryResults.estimatedTotalHits}</Text> 49 {/* Date of creation */} 50 <Text style={{ fontSize: 10, textAlign: 'center', marginTop: 10 }}>Created at: {new Date().toLocaleString()}</Text> 51 </View> 52 </View> 53 </Page> 54 <Page size="A4" style={styles.page}> 55 <View> 56 <View style={{ fontSize: 10 }}> 57 <Text>Search results for: "{query}"</Text> 58 <Text>Number of Results: {queryResults.estimatedTotalHits}</Text> 59 </View> 60 <View> 61 {queryResults.hits.map((result, index) => ( 62 <View 63 wrap={result.content.body?.length > 500} 64 key={index} 65 style={{ 66 flexDirection: 'column', 67 margin: 8, 68 padding: 12, 69 backgroundColor: '#e6e6e6', 70 gap: 16, 71 borderTopLeftRadius: borderRadius, 72 borderTopRightRadius: borderRadius, 73 borderBottomLeftRadius: borderRadius, 74 borderBottomRightRadius: borderRadius, 75 borderStyle: 'solid', 76 borderWidth: 1, 77 borderColor: '#00000' 78 }}> 79 <View 80 style={{ flexDirection: 'row', justifyContent: 'space-between', fontSize: 10 }}> 81 <Text>{result.room_name ?? result.room_id}</Text> 82 <View style={{ flexDirection: 'row' }}> 83 <Text>{result.sender}</Text> 84 <Link style={{ marginLeft: 5 }} src={`https://matrix.to/#/${result.room_id}/${result.id}`}>Link</Link> 85 </View> 86 </View> 87 <Text>{result.content.body}</Text> 88 </View> 89 ))} 90 </View> 91 <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 10, fontSize: 10 }} fixed> 92 <Text render={({ pageNumber, totalPages }) => ( 93 `${pageNumber} / ${totalPages}` 94 )} /> 95 <Text> 96 Matrix Search Engine 97 </Text> 98 </View> 99 </View> 100 </Page> 101 </Document > 102 ) 103 } 104 105 export async function renderPDFToDisk(query: string, room_id?: string, sender?: string, includeRedactions: boolean = false, includeEdits: boolean = false) { 106 let cleanedRoomId = room_id?.trim(); 107 let cleanedSender = sender?.trim(); 108 if (cleanedRoomId === "") { 109 cleanedRoomId = undefined; 110 } 111 if (cleanedSender === "") { 112 cleanedSender = undefined; 113 } 114 const queryResults = await indexer.search(query, cleanedRoomId, cleanedSender); 115 116 // Filter out redactions if the user does not want them 117 if (!includeRedactions) { 118 queryResults.hits = queryResults.hits.filter((hit) => !hit.redacted); 119 } 120 // Filter out edits if the user does not want them 121 if (!includeEdits) { 122 queryResults.hits = queryResults.hits.filter((hit) => !hit.edited); 123 } 124 125 //console.log(`Search results:`, queryResults); 126 await ReactPDF.renderToFile(<MessageDocument query={query} room_id={cleanedRoomId} sender={cleanedSender} queryResults={queryResults} />, `output.pdf`); 127 } 128 129 export async function renderPDFToBufferWithData(queryResults: { hits: Hits<Record<string, any>>; estimatedTotalHits: number; }, query?: string, room_id?: string, sender?: string,): Promise<Buffer> { 130 return await renderToBuffer(<MessageDocument query={query ?? ""} room_id={room_id} sender={sender} queryResults={queryResults} />); 131 } 132 133 134 const pathToThisFile = resolve(fileURLToPath(import.meta.url)) 135 const pathPassedToNode = resolve(process.argv[1]) 136 const isThisFileBeingRunViaCLI = pathToThisFile.includes(pathPassedToNode) 137 138 if (isThisFileBeingRunViaCLI) { 139 const questions: PromptObject<string>[] = [ 140 { 141 type: 'text', 142 name: 'query', 143 initial: "", 144 message: 'Enter your search query:', 145 }, 146 { 147 type: 'text', 148 name: 'room_id', 149 initial: undefined, 150 message: 'Enter the Room ID:', 151 }, 152 { 153 type: 'text', 154 name: 'sender', 155 initial: undefined, 156 message: 'Enter the sender:', 157 }, 158 // Ask if the redactions should be included 159 { 160 type: 'confirm', 161 name: 'include_redactions', 162 initial: false, 163 message: 'Include redactions in the search results?', 164 }, 165 // Ask if the edits should be included 166 { 167 type: 'confirm', 168 name: 'include_edits', 169 initial: false, 170 message: 'Include edits in the search results?', 171 } 172 ] 173 174 const response = await prompts(questions); 175 176 await renderPDFToDisk(response.query as string, response.room_id as string | undefined, response.sender as string | undefined, response.include_redactions as boolean, response.include_edits as boolean); 177 console.log("PDF rendered to disk"); 178 }