diff --git a/Glance.xcodeproj/project.pbxproj b/Glance.xcodeproj/project.pbxproj index 4903bb3..cadb160 100644 --- a/Glance.xcodeproj/project.pbxproj +++ b/Glance.xcodeproj/project.pbxproj @@ -14,6 +14,10 @@ 7E1DC528240E6F4A00D0A061 /* RendererFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E1DC527240E6F4A00D0A061 /* RendererFactory.swift */; }; 7E1DC52A240E6FDE00D0A061 /* CodeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E1DC529240E6FDE00D0A061 /* CodeRenderer.swift */; }; 7E35BFC42410FDD800AB0A3C /* minireset.min.css in Resources */ = {isa = PBXBuildFile; fileRef = 7E35BFC32410FDD800AB0A3C /* minireset.min.css */; }; + 7E413F802418DD6200CFBB1D /* CsvRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E413F7F2418DD6200CFBB1D /* CsvRenderer.swift */; }; + 7E413F822418EB4B00CFBB1D /* csv-papaparse.min.js in Resources */ = {isa = PBXBuildFile; fileRef = 7E413F812418EB4B00CFBB1D /* csv-papaparse.min.js */; }; + 7E413F842418EB7E00CFBB1D /* csv-main.css in Resources */ = {isa = PBXBuildFile; fileRef = 7E413F832418EB7E00CFBB1D /* csv-main.css */; }; + 7E413F862418EEC100CFBB1D /* csv-main.js in Resources */ = {isa = PBXBuildFile; fileRef = 7E413F852418EEC100CFBB1D /* csv-main.js */; }; 7E59DDD7240CC3B2009A4E05 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 7E59DDD6240CC3B2009A4E05 /* .gitignore */; }; 7E6EF1FD240CC802009E4199 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E6EF1FC240CC802009E4199 /* Quartz.framework */; }; 7E6EF200240CC802009E4199 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E6EF1FF240CC802009E4199 /* PreviewViewController.swift */; }; @@ -77,6 +81,10 @@ 7E1DC527240E6F4A00D0A061 /* RendererFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RendererFactory.swift; sourceTree = ""; }; 7E1DC529240E6FDE00D0A061 /* CodeRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeRenderer.swift; sourceTree = ""; }; 7E35BFC32410FDD800AB0A3C /* minireset.min.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = minireset.min.css; path = QLPlugin/Assets/minireset.min.css; sourceTree = SOURCE_ROOT; }; + 7E413F7F2418DD6200CFBB1D /* CsvRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CsvRenderer.swift; sourceTree = ""; }; + 7E413F812418EB4B00CFBB1D /* csv-papaparse.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "csv-papaparse.min.js"; sourceTree = ""; }; + 7E413F832418EB7E00CFBB1D /* csv-main.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = "csv-main.css"; sourceTree = ""; }; + 7E413F852418EEC100CFBB1D /* csv-main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "csv-main.js"; sourceTree = ""; }; 7E59DDD6240CC3B2009A4E05 /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitignore; sourceTree = SOURCE_ROOT; }; 7E6EF1FA240CC802009E4199 /* QLPlugin.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = QLPlugin.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7E6EF1FC240CC802009E4199 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; @@ -147,6 +155,7 @@ isa = PBXGroup; children = ( 7E1DC52D240E9D2C00D0A061 /* code */, + 7E413F7E2418DD3000CFBB1D /* csv */, 7E9F0D622416563E007F1008 /* markdown */, 7E35BFC32410FDD800AB0A3C /* minireset.min.css */, 7E1DC50A240E5A4B00D0A061 /* shared.css */, @@ -158,6 +167,7 @@ isa = PBXGroup; children = ( 7E1DC529240E6FDE00D0A061 /* CodeRenderer.swift */, + 7E413F7F2418DD6200CFBB1D /* CsvRenderer.swift */, 7E1DC520240E6D8000D0A061 /* MarkdownRenderer.swift */, ); path = FileTypeRenderers; @@ -171,6 +181,16 @@ path = code; sourceTree = ""; }; + 7E413F7E2418DD3000CFBB1D /* csv */ = { + isa = PBXGroup; + children = ( + 7E413F832418EB7E00CFBB1D /* csv-main.css */, + 7E413F852418EEC100CFBB1D /* csv-main.js */, + 7E413F812418EB4B00CFBB1D /* csv-papaparse.min.js */, + ); + path = csv; + sourceTree = ""; + }; 7E6EF1FB240CC802009E4199 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -358,6 +378,9 @@ 7E35BFC42410FDD800AB0A3C /* minireset.min.css in Resources */, 7E9F0D7424168685007F1008 /* github-markdown-css.min.css in Resources */, 7E9F0D6124164D35007F1008 /* code.css in Resources */, + 7E413F862418EEC100CFBB1D /* csv-main.js in Resources */, + 7E413F822418EB4B00CFBB1D /* csv-papaparse.min.js in Resources */, + 7E413F842418EB7E00CFBB1D /* csv-main.css in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -386,6 +409,7 @@ 7E6EF200240CC802009E4199 /* PreviewViewController.swift in Sources */, 7E1DC528240E6F4A00D0A061 /* RendererFactory.swift in Sources */, 7E1DC521240E6D8000D0A061 /* MarkdownRenderer.swift in Sources */, + 7E413F802418DD6200CFBB1D /* CsvRenderer.swift in Sources */, 7E9F0D57241625A9007F1008 /* Shell.swift in Sources */, 7E1DC52A240E6FDE00D0A061 /* CodeRenderer.swift in Sources */, 7E9F0D7E24168870007F1008 /* Script.swift in Sources */, diff --git a/QLPlugin/Assets/csv/csv-main.css b/QLPlugin/Assets/csv/csv-main.css new file mode 100644 index 0000000..7c6ab59 --- /dev/null +++ b/QLPlugin/Assets/csv/csv-main.css @@ -0,0 +1,13 @@ +table { + border-collapse: collapse; +} + +table, +td { + padding: 5px 7px; + border: 1px solid var(--color-border); +} + +tr:first-child td { + background: var(--color-border); +} diff --git a/QLPlugin/Assets/csv/csv-main.js b/QLPlugin/Assets/csv/csv-main.js new file mode 100644 index 0000000..295e486 --- /dev/null +++ b/QLPlugin/Assets/csv/csv-main.js @@ -0,0 +1,19 @@ +function render(fileContent) { + // Create HTML table + const previewDiv = document.getElementById("csv-preview"); + const tableElement = document.createElement("table"); + previewDiv.append(tableElement); + + function appendRows(rows) { + const rowElements = rows + .map(row => `${row.map(column => `${column}`).join("\n")}`) + .join("\n"); + tableElement.innerHTML = rowElements; + } + + // Parse CSV + Papa.parse(fileContent, { + worker: true, + chunk: results => appendRows(results.data), + }); +} diff --git a/QLPlugin/Assets/csv/csv-papaparse.min.js b/QLPlugin/Assets/csv/csv-papaparse.min.js new file mode 100644 index 0000000..599f4ba --- /dev/null +++ b/QLPlugin/Assets/csv/csv-papaparse.min.js @@ -0,0 +1,25 @@ +/** + * Papa Parse v5.0.2 + * https://github.com/mholt/PapaParse + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Matthew Holt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof module&&"undefined"!=typeof exports?module.exports=t():e.Papa=t()}(this,function s(){"use strict";var f="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==f?f:{};var n=!f.document&&!!f.postMessage,o=n&&/blob:/i.test((f.location||{}).protocol),a={},h=0,b={parse:function(e,t){var r=(t=t||{}).dynamicTyping||!1;q(r)&&(t.dynamicTypingFunction=r,r={});if(t.dynamicTyping=r,t.transform=!!q(t.transform)&&t.transform,t.worker&&b.WORKERS_SUPPORTED){var i=function(){if(!b.WORKERS_SUPPORTED)return!1;var e=(r=f.URL||f.webkitURL||null,i=s.toString(),b.BLOB_URL||(b.BLOB_URL=r.createObjectURL(new Blob(["(",i,")();"],{type:"text/javascript"})))),t=new f.Worker(e);var r,i;return t.onmessage=_,t.id=h++,a[t.id]=t}();return i.userStep=t.step,i.userChunk=t.chunk,i.userComplete=t.complete,i.userError=t.error,t.step=q(t.step),t.chunk=q(t.chunk),t.complete=q(t.complete),t.error=q(t.error),delete t.worker,void i.postMessage({input:e,config:t,workerId:i.id})}var n=null;b.NODE_STREAM_INPUT,"string"==typeof e?n=t.download?new l(t):new p(t):!0===e.readable&&q(e.read)&&q(e.on)?n=new m(t):(f.File&&e instanceof File||e instanceof Object)&&(n=new c(t));return n.stream(e)},unparse:function(e,t){var i=!1,_=!0,g=",",v="\r\n",n='"',s=n+n,r=!1,a=null;!function(){if("object"!=typeof t)return;"string"!=typeof t.delimiter||b.BAD_DELIMITERS.filter(function(e){return-1!==t.delimiter.indexOf(e)}).length||(g=t.delimiter);("boolean"==typeof t.quotes||Array.isArray(t.quotes))&&(i=t.quotes);"boolean"!=typeof t.skipEmptyLines&&"string"!=typeof t.skipEmptyLines||(r=t.skipEmptyLines);"string"==typeof t.newline&&(v=t.newline);"string"==typeof t.quoteChar&&(n=t.quoteChar);"boolean"==typeof t.header&&(_=t.header);if(Array.isArray(t.columns)){if(0===t.columns.length)throw new Error("Option columns is empty");a=t.columns}void 0!==t.escapeChar&&(s=t.escapeChar+n)}();var o=new RegExp(U(n),"g");"string"==typeof e&&(e=JSON.parse(e));if(Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return u(null,e,r);if("object"==typeof e[0])return u(a||h(e[0]),e,r)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:h(e.data[0])),Array.isArray(e.data[0])||"object"==typeof e.data[0]||(e.data=[e.data])),u(e.fields||[],e.data||[],r);throw new Error("Unable to serialize unrecognized input");function h(e){if("object"!=typeof e)return[];var t=[];for(var r in e)t.push(r);return t}function u(e,t,r){var i="";"string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t));var n=Array.isArray(e)&&0=this._config.preview;if(o)f.postMessage({results:n,workerId:b.WORKER_ID,finished:a});else if(q(this._config.chunk)&&!t){if(this._config.chunk(n,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);n=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(n.data),this._completeResults.errors=this._completeResults.errors.concat(n.errors),this._completeResults.meta=n.meta),this._completed||!a||!q(this._config.complete)||n&&n.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),a||n&&n.meta.paused||this._nextChunk(),n}this._halted=!0},this._sendError=function(e){q(this._config.error)?this._config.error(e):o&&this._config.error&&f.postMessage({workerId:b.WORKER_ID,error:e,finished:!1})}}function l(e){var i;(e=e||{}).chunkSize||(e.chunkSize=b.RemoteChunkSize),u.call(this,e),this._nextChunk=n?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(i=new XMLHttpRequest,this._config.withCredentials&&(i.withCredentials=this._config.withCredentials),n||(i.onload=y(this._chunkLoaded,this),i.onerror=y(this._chunkError,this)),i.open("GET",this._input,!n),this._config.downloadRequestHeaders){var e=this._config.downloadRequestHeaders;for(var t in e)i.setRequestHeader(t,e[t])}if(this._config.chunkSize){var r=this._start+this._config.chunkSize-1;i.setRequestHeader("Range","bytes="+this._start+"-"+r)}try{i.send()}catch(e){this._chunkError(e.message)}n&&0===i.status?this._chunkError():this._start+=this._config.chunkSize}},this._chunkLoaded=function(){4===i.readyState&&(i.status<200||400<=i.status?this._chunkError():(this._finished=!this._config.chunkSize||this._start>function(e){var t=e.getResponseHeader("Content-Range");if(null===t)return-1;return parseInt(t.substr(t.lastIndexOf("/")+1))}(i),this.parseChunk(i.responseText)))},this._chunkError=function(e){var t=i.statusText||e;this._sendError(new Error(t))}}function c(e){var i,n;(e=e||{}).chunkSize||(e.chunkSize=b.LocalChunkSize),u.call(this,e);var s="undefined"!=typeof FileReader;this.stream=function(e){this._input=e,n=e.slice||e.webkitSlice||e.mozSlice,s?((i=new FileReader).onload=y(this._chunkLoaded,this),i.onerror=y(this._chunkError,this)):i=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(i.error)}}function p(e){var r;u.call(this,e=e||{}),this.stream=function(e){return r=e,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var e=this._config.chunkSize,t=e?r.substr(0,e):r;return r=e?r.substr(e):"",this._finished=!r,this.parseChunk(t)}}}function m(e){u.call(this,e=e||{});var t=[],r=!0,i=!1;this.pause=function(){u.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){u.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){i&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):r=!0},this._streamData=y(function(e){try{t.push("string"==typeof e?e:e.toString(this._config.encoding)),r&&(r=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=y(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=y(function(){this._streamCleanUp(),i=!0,this._streamData("")},this),this._streamCleanUp=y(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function r(g){var a,o,h,i=Math.pow(2,53),n=-i,s=/^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i,u=/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/,t=this,r=0,f=0,d=!1,e=!1,l=[],c={data:[],errors:[],meta:{}};if(q(g.step)){var p=g.step;g.step=function(e){if(c=e,_())m();else{if(m(),0===c.data.length)return;r+=e.data.length,g.preview&&r>g.preview?o.abort():p(c,t)}}}function v(e){return"greedy"===g.skipEmptyLines?""===e.join("").trim():1===e.length&&0===e[0].length}function m(){if(c&&h&&(k("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+b.DefaultDelimiter+"'"),h=!1),g.skipEmptyLines)for(var e=0;e=l.length?"__parsed_extra":l[r]),g.transform&&(s=g.transform(s,n)),s=y(n,s),"__parsed_extra"===n?(i[n]=i[n]||[],i[n].push(s)):i[n]=s}return g.header&&(r>l.length?k("FieldMismatch","TooManyFields","Too many fields: expected "+l.length+" fields but parsed "+r,f+t):r=i.length/2?"\r\n":"\r"}(e,i)),h=!1,g.delimiter)q(g.delimiter)&&(g.delimiter=g.delimiter(e),c.meta.delimiter=g.delimiter);else{var n=function(e,t,r,i,n){var s,a,o,h;n=n||[",","\t","|",";",b.RECORD_SEP,b.UNIT_SEP];for(var u=0;u=L)return R(!0)}else for(g=M,M++;;){if(-1===(g=a.indexOf(O,g+1)))return t||u.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:h.length,index:M}),w();if(g===i-1)return w(a.substring(M,g).replace(_,O));if(O!==z||a[g+1]!==z){if(O===z||0===g||a[g-1]!==z){var y=E(-1===m?p:Math.min(p,m));if(a[g+1+y]===D){f.push(a.substring(M,g).replace(_,O)),a[M=g+1+y+e]!==O&&(g=a.indexOf(O,M)),p=a.indexOf(D,M),m=a.indexOf(I,M);break}var k=E(m);if(a.substr(g+1+k,n)===I){if(f.push(a.substring(M,g).replace(_,O)),C(g+1+k+n),p=a.indexOf(D,M),g=a.indexOf(O,M),o&&(S(),j))return R();if(L&&h.length>=L)return R(!0);break}u.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:h.length,index:M}),g++}}else g++}return w();function b(e){h.push(e),d=M}function E(e){var t=0;if(-1!==e){var r=a.substring(g+1,e);r&&""===r.trim()&&(t=r.length)}return t}function w(e){return t||(void 0===e&&(e=a.substr(M)),f.push(e),M=i,b(f),o&&S()),R()}function C(e){M=e,b(f),f=[],m=a.indexOf(I,M)}function R(e,t){return{data:t||!1?h[0]:h,errors:u,meta:{delimiter:D,linebreak:I,aborted:j,truncated:!!e,cursor:d+(r||0)}}}function S(){A(R(void 0,!0)),h=[],u=[]}function x(e,t,r){var i={nextDelim:void 0,quoteSearch:void 0},n=a.indexOf(O,t+1);if(t + + + + + + + + + + + + +
+ + + + + + diff --git a/QLPlugin/Assets/shared.css b/QLPlugin/Assets/shared.css index 721cbd3..0bdb542 100644 --- a/QLPlugin/Assets/shared.css +++ b/QLPlugin/Assets/shared.css @@ -1,3 +1,19 @@ +/* Variables */ +body { + --color-background: #ffffff; + --color-border: #b5b5b5; + --color-text: #000000; +} + +@media (prefers-color-scheme: dark) { + /* Dark mode */ + body { + --color-background: #1e1e1e; + --color-border: #6f6f6f; + --color-text: #ffffff; + } +} + body { font-family: -apple-system, BlinkMacSystemFont, "Helvetica", "Arial", sans-serif; font-size: 12px; @@ -5,6 +21,8 @@ body { padding: 12px; line-height: 1.4; overflow: auto; + background: var(--color-background); + color: var(--color-text); } /* Large previews (e.g. Quick Look) */ @@ -14,14 +32,6 @@ body { } } -/* Dark mode */ -@media (prefers-color-scheme: dark) { - body { - background: #1e1e1e; - color: #ffffff; - } -} - pre, code { font-family: Menlo, monospace; diff --git a/QLPlugin/Info.plist b/QLPlugin/Info.plist index 92f83cd..6f697e0 100644 --- a/QLPlugin/Info.plist +++ b/QLPlugin/Info.plist @@ -155,6 +155,11 @@ dyn.ah62d4rv4ge81g25xsq dyn.ah62d4rv4ge81g6pq dyn.ah62d4rv4ge80g55sq2 + + + dyn.ah62d4rv4ge81k2pc + public.comma-separated-values-text + public.tab-separated-values-text QLSupportsSearchableItems diff --git a/QLPlugin/Renderers/FileTypeRenderers/CodeRenderer.swift b/QLPlugin/Renderers/FileTypeRenderers/CodeRenderer.swift index 2725bbd..42ed5fd 100644 --- a/QLPlugin/Renderers/FileTypeRenderers/CodeRenderer.swift +++ b/QLPlugin/Renderers/FileTypeRenderers/CodeRenderer.swift @@ -1,5 +1,4 @@ import Foundation -import JavaScriptCore import os.log class CodeRenderer: Renderer { diff --git a/QLPlugin/Renderers/FileTypeRenderers/CsvRenderer.swift b/QLPlugin/Renderers/FileTypeRenderers/CsvRenderer.swift new file mode 100644 index 0000000..e01691a --- /dev/null +++ b/QLPlugin/Renderers/FileTypeRenderers/CsvRenderer.swift @@ -0,0 +1,51 @@ +import Foundation +import os.log + +class CsvRenderer: Renderer { + private let cssUrl = Bundle.main.url(forResource: "csv-main", withExtension: "css") + private let papaParsejsUrl = Bundle.main.url( + forResource: "csv-papaparse.min", + withExtension: "js" + ) + private let renderjsUrl = Bundle.main.url(forResource: "csv-main", withExtension: "js") + + override func getStylesheets() -> [Stylesheet] { + var stylesheets = super.getStylesheets() + if let cssUrl = cssUrl { + stylesheets.append(Stylesheet(url: cssUrl)) + } else { + os_log("Could not find main CSV stylesheet", type: .error) + } + return stylesheets + } + + override func getHtml() -> String { + "
" + } + + override func getScripts() -> [Script] { + var scripts = super.getScripts() + + // Papa Parse library (parses CSV files) + if let papaParsejsUrl = papaParsejsUrl { + scripts.append(Script(url: papaParsejsUrl)) + } else { + os_log("Could not find Papa Parse script", type: .error) + } + + // Render script (generates HTML table from parsed CSV) + if let renderjsUrl = renderjsUrl { + scripts.append(Script(url: renderjsUrl)) + } else { + os_log("Could not find CSV render script", type: .error) + } + + // Main script (calls render function with file content) + scripts.append(Script(content: """ + const fileContent = `\(fileContent.replacingOccurrences(of: "`", with: "\\`"))`; + render(fileContent); + """)) + + return scripts + } +} diff --git a/QLPlugin/Renderers/RendererFactory.swift b/QLPlugin/Renderers/RendererFactory.swift index 77e2b9c..33c241a 100644 --- a/QLPlugin/Renderers/RendererFactory.swift +++ b/QLPlugin/Renderers/RendererFactory.swift @@ -8,6 +8,8 @@ class RendererFactory { ) -> Renderer { var renderer: Renderer.Type switch fileExtension { + case "csv", "tab", "tsv": + renderer = CsvRenderer.self case "md", "markdown", "mdown", "mkdn", "mkd": renderer = MarkdownRenderer.self default: