2025-03-25 03:55:52 +00:00
import { App , MarkdownView , Notice , Plugin , PluginSettingTab , Setting , FileView } from 'obsidian' ;
// import { inflate, deflate } from "./util/pako"
2025-03-10 15:02:54 +00:00
// import { encodeSafe, decodeSafe } from "./util/runlength"
2025-03-25 03:55:52 +00:00
import { netcompress , netdecompress , plugincompress , plugindecompress } from "./util/networkmastercompression"
2025-03-08 23:50:12 +00:00
interface compressorSettingsData {
// mySetting: string;
PrintResult : boolean ;
FileSize : boolean ;
LeaveRawLinks : boolean ;
2025-03-10 12:33:18 +00:00
Debug : boolean ;
2025-03-27 05:21:13 +00:00
// PakoCompress: boolean;
2025-03-25 03:55:52 +00:00
ExcalidrawPrecision : number ;
2025-03-08 23:50:12 +00:00
}
const DEFAULT_SETTINGS : compressorSettingsData = {
// mySetting: 'default',
2025-03-25 03:55:52 +00:00
PrintResult : false ,
FileSize : false ,
2025-03-10 12:33:18 +00:00
LeaveRawLinks : true ,
2025-03-25 03:55:52 +00:00
Debug : false ,
2025-03-27 05:21:13 +00:00
// PakoCompress: true,
2025-03-25 03:55:52 +00:00
ExcalidrawPrecision : 2 ,
2025-03-08 23:50:12 +00:00
}
2025-03-10 12:33:18 +00:00
let globalLeafs : any [ ] = [ ]
let NoticePool : Notice [ ] = [ ]
let statusBarItemEl2 : ( undefined | HTMLElement ) ;
2025-03-08 23:50:12 +00:00
export default class compressorPlugin extends Plugin {
settings : compressorSettingsData ;
async onload() {
2025-03-10 12:33:18 +00:00
let loadTime = new Date ( ) . getTime ( )
2025-03-08 23:50:12 +00:00
this . registerExtensions ( [ "ctxt" ] , "ctxt" )
this . registerView ( "ctxt" ,
( leaf ) = > {
2025-03-10 12:33:18 +00:00
let hook = new MarkdownView ( leaf )
let f = setInterval ( ( ) = > {
let file = hook . file || this . app . workspace . getActiveFile ( )
2025-03-08 23:50:12 +00:00
if ( file ) {
clearInterval ( f )
if ( file && file . extension == "ctxt" ) {
//attempt to stop obsidian changing contents to compressed
hook . unload ( )
hook . canAcceptExtension ( "" )
hook . editor . refresh = ( ) = > { }
globalLeafs . push ( [ "ctxt" , file . path , hook ] )
this . app . vault . read ( file ) . then ( ( data ) = > {
2025-03-11 00:27:05 +00:00
// if (file && data && (!data.includes("\n") && !data.includes(" "))) {
if ( file && data && checkCompFile ( data ) ) {
2025-03-08 23:50:12 +00:00
new Notice ( "Attempting to load." )
setTimeout ( ( ) = > {
2025-03-10 12:33:18 +00:00
let decompress = null
2025-03-08 23:50:12 +00:00
try {
2025-03-10 12:33:18 +00:00
decompress = decompressfile ( data , this . settings . Debug || false )
2025-03-08 23:50:12 +00:00
} catch ( err ) { console . log ( err ) }
if ( decompress ) {
hook . setViewData ( decompress , true )
//attempt to stop obsidian changing contents to compressed
hook . unload ( )
hook . editor . refresh = ( ) = > { }
}
} , 50 )
}
} )
}
}
} , 10 )
return hook
}
)
2025-03-09 11:05:43 +00:00
this . registerEvent ( this . app . workspace . on ( "file-open" , ( file ) = > {
2025-03-08 23:50:12 +00:00
if ( file && file . extension == "ctxt" && globalLeafs . length <= 0 && ( new Date ( ) . getTime ( ) - loadTime ) < 1000 ) {
this . app . workspace . getMostRecentLeaf ( ) ? . openFile ( file )
}
if ( file ) {
this . app . vault . read ( file ) . then ( ( data ) = > {
if ( statusBarItemEl2 && this . settings . FileSize ) statusBarItemEl2 . setText ( data . length + "B" )
} )
}
2025-03-09 11:05:43 +00:00
} ) )
2025-03-08 23:50:12 +00:00
this . registerEvent ( this . app . vault . on ( 'modify' , ( file ) = > {
2025-03-10 12:33:18 +00:00
let file2 = this . app . vault . getFileByPath ( file . path )
2025-03-08 23:50:12 +00:00
if ( file2 && file2 . extension == "ctxt" ) {
this . app . vault . read ( file2 ) . then ( ( data ) = > {
if ( statusBarItemEl2 && this . settings . FileSize ) statusBarItemEl2 . setText ( data . length + "B" )
2025-03-11 00:27:05 +00:00
// if (file2 && data && (data.includes("\n") || data.includes(" "))) {
2025-03-25 03:55:52 +00:00
if ( file2 && data && ! checkCompFile ( data ) ) {
2025-03-10 12:33:18 +00:00
let compress = null
2025-03-08 23:50:12 +00:00
try {
NoticePool . forEach ( ( n ) = > {
if ( n ) n . hide ( )
} )
2025-03-10 12:33:18 +00:00
compress = compressfile ( data , this . settings . Debug || false , this . settings )
2025-03-08 23:50:12 +00:00
} catch ( err ) { console . log ( err ) }
if ( compress ) {
globalLeafs . forEach ( ( leaf ) = > {
if ( leaf [ 0 ] == "ctxt" && leaf [ 2 ] == file . path && leaf [ 3 ] ) {
// leaf[3].editor.setLine("")
leaf [ 3 ] . unload ( )
}
} )
this . app . vault . modify ( file2 , compress )
globalLeafs . forEach ( ( leaf ) = > {
if ( leaf [ 0 ] == "ctxt" && leaf [ 2 ] == file . path && leaf [ 3 ] ) {
// leaf[3].editor.setLine("")
leaf [ 3 ] . setViewData ( data , true )
}
} )
}
}
} )
} else if ( file2 ) {
this . app . vault . read ( file2 ) . then ( ( data ) = > {
if ( statusBarItemEl2 && this . settings . FileSize ) statusBarItemEl2 . setText ( data . length + "B" )
} )
}
} ) ) ;
await this . loadSettings ( ) ;
// const ribbonIconEl = this.addRibbonIcon('sheets-in-box', 'Compress/Decompress', (evt: MouseEvent) => {
// this.startAction()
// });
const ribbonIconEl2 = this . addRibbonIcon ( 'checkmark' , 'Create new ctxt' , ( evt : MouseEvent ) = > {
2025-03-10 12:33:18 +00:00
let filen = ` newctxtfile ${ ( new Date ( ) . getTime ( ) . toString ( ) . substring ( 10 ) ) } ${ Math . floor ( Math . random ( ) * 500 ) } .ctxt `
2025-03-08 23:50:12 +00:00
this . app . vault . create ( "./" + filen , "Write away!" ) //.then((filex) => {
// setTimeout(()=>{
2025-03-10 12:33:18 +00:00
// let file = this.app.vault.getFileByPath(filen)
2025-03-08 23:50:12 +00:00
// if (file) {
// console.log(file, this.app.workspace.getMostRecentLeaf())
// this.app.workspace.getMostRecentLeaf()?.openFile(file)
// }
// },100)
//filex null, file null, pain null
// })
} ) ;
const ribbonIconEl3 = this . addRibbonIcon ( 'up-and-down-arrows' , 'Convert currently opened file' , ( evt : MouseEvent ) = > {
if ( confirm ( "Are you sure you want to convert" ) ) { //will this work? electron confirms dont exit. Oh it does
2025-03-10 12:33:18 +00:00
let file = this . app . workspace . getActiveFile ( )
2025-03-08 23:50:12 +00:00
if ( file ) {
if ( file . extension == "ctxt" ) {
this . app . vault . read ( file ) . then ( ( data ) = > {
2025-03-10 12:33:18 +00:00
let raw = data
2025-03-08 23:50:12 +00:00
try {
2025-03-10 12:33:18 +00:00
let dc = decompressfile ( data , this . settings . Debug || false )
2025-03-08 23:50:12 +00:00
if ( dc ) raw = dc
} catch ( _ ) {
new Notice ( "Failed to decompress, plainText?" )
}
if ( raw && file ) {
this . app . vault . rename ( file , file . path . substring ( 0 , file . path . length - ( file . extension . length + 1 ) ) + ".md" ) . then ( ( ) = > {
if ( raw && file ) {
this . app . vault . modify ( file , raw )
}
} )
// this.app.vault.create(file.path+".md",raw).then(()=>{
// if(file) {
// this.app.vault.delete(file,false).then(()=>new Notice("Success!"))
// }
// })
} else {
new Notice ( "Failed." )
}
} )
} else {
this . app . vault . read ( file ) . then ( ( data ) = > {
2025-03-10 12:33:18 +00:00
let raw = data
2025-03-08 23:50:12 +00:00
try {
2025-03-10 12:33:18 +00:00
let dc = compressfile ( data , this . settings . Debug || false )
2025-03-08 23:50:12 +00:00
if ( dc ) raw = dc
} catch ( _ ) {
new Notice ( "Failed to compress." )
}
if ( raw && file ) {
this . app . vault . rename ( file , file . path . substring ( 0 , file . path . length - ( file . extension . length + 1 ) ) + ".ctxt" ) . then ( ( ) = > {
if ( raw && file ) {
this . app . vault . modify ( file , raw ) . then ( ( ) = > {
setTimeout ( ( ) = > { if ( file ) this . app . workspace . getMostRecentLeaf ( ) ? . openFile ( file ) ; } , 100 )
setTimeout ( ( ) = > { if ( file ) this . app . workspace . getMostRecentLeaf ( ) ? . openFile ( file ) } , 300 )
setTimeout ( ( ) = > alert ( "Complete. The cursor will be invisible, press off of obsidian and back." ) , 500 )
// setTimeout(() => { if (file) this.app.workspace.getMostRecentLeaf()?.detach() }, 300)
} )
}
} )
} else {
new Notice ( "Failed." )
}
} )
}
}
}
} ) ;
ribbonIconEl2 . addClass ( 'compressor-class' ) ;
ribbonIconEl3 . addClass ( 'compressor-class' ) ;
//set elsewhere
statusBarItemEl2 = this . addStatusBarItem ( ) ;
statusBarItemEl2 . setText ( "" ) ;
2025-03-25 03:55:52 +00:00
statusBarItemEl2 . title = "File size"
2025-03-08 23:50:12 +00:00
this . addCommand ( {
id : 'createctxt' ,
name : 'Create a new ctxt file in root' ,
callback : ( ) = > {
2025-03-10 12:33:18 +00:00
let filen = ` newctxtfile ${ ( new Date ( ) . getTime ( ) . toString ( ) . substring ( 10 ) ) } ${ Math . floor ( Math . random ( ) * 500 ) } .ctxt `
2025-03-08 23:50:12 +00:00
this . app . vault . create ( "./" + filen , "Write away!" )
}
} ) ;
this . addSettingTab ( new compressorSettings ( this . app , this ) ) ;
// this.registerInterval(window.setInterval(() => {
2025-03-10 12:33:18 +00:00
// let file = this.app.workspace.getActiveFile()
2025-03-08 23:50:12 +00:00
// if (file != null) {
// this.app.vault.read(file).then((data) => {
// if (!data.includes("\n") && /[A-z0-9\-_]/gm.test(data) && !data.includes(" ")) {
2025-03-10 12:33:18 +00:00
// let isctxt = false
2025-03-09 11:05:43 +00:00
// try {
// decompressfile(data)
// isctxt = true
// } catch (_) { }
// if (isctxt) {
// if (pane) pane.innerHTML += `<div id="AEDTCOMPDNEOUTSNTTIS!" style="position: absolute;left:0%;right:0%;width:100%;height:100%;background-color:rgba(20,20,20,0.6);z-index: 99999999999;">Content Compressed.</div>`
2025-03-08 23:50:12 +00:00
// }
// }
// })
// const pane = document.querySelector("body > div.app-container > div.horizontal-main-container > div > div.workspace-split.mod-vertical.mod-root > div > div.workspace-tab-container > div.workspace-leaf.mod-active > div > div.view-content > div.markdown-source-view.cm-s-obsidian.mod-cm6.node-insert-event.is-readable-line-width.is-live-preview.is-folding.show-properties > div")
// if (document.getElementById("AEDTCOMPDNEOUTSNTTIS!")) document.getElementById("AEDTCOMPDNEOUTSNTTIS!")?.remove()
// }
// }, 100));
//<div style="position: absolute;left:0%;right:0%;width:100%;height:100%;background-color:rgba(20,20,20,0.6);z-index: 99999999999;">Content Compressed.</div>
2025-03-25 03:55:52 +00:00
//NOT FINISHED.
// ; (async () => {
// while (true) {
// await new Promise((r) => { setTimeout(r, 100) })
// if (this.app.vault.getFiles().length > 0) break
// }
// var hn = false
// this.app.vault.getFiles().forEach((file) => {
// console.log(file.name)
// this.app.vault.read(file).then((data) => {
// if (file) {
// if (data.includes("excalidraw") && data.includes("```compressed-json") && !hn) {
// alert("File Compressor: You have excalidraw installed with compression, please go to settings -> excalidraw -> Saving -> Compression and disable it. That way it can be compressed by this plugin. Source cause: " + file.path)
// hn = true
// }
// // var out = plugindecompress(file, data)
// plugincompress(file, data, this.settings)
// var out = plugindecompress(file, data)
// // if (out) this.app.vault.modify(file, out)
// console.log("WRITER DISABLED.")
// }
// })
// })
// })()
this . app . workspace . on ( "quit" , ( ) = > {
//NOT FINISHED.
// this.app.vault.getFiles().forEach((file) => {
// console.log(file.name)
// this.app.vault.read(file).then((data) => {
// if (file) {
// var out = plugincompress(file, data)
// if (out) this.app.vault.modify(file, out)
// }
// })
// })
} )
2025-03-08 23:50:12 +00:00
}
onunload() {
2025-03-25 03:55:52 +00:00
// alert("In order to view CTXT files you will need to re-enable the plugin. If you are deleting the plugin, please convert your files back before.")
2025-03-08 23:50:12 +00:00
}
async loadSettings() {
this . settings = Object . assign ( { } , DEFAULT_SETTINGS , await this . loadData ( ) ) ;
}
async saveSettings() {
await this . saveData ( this . settings ) ;
}
}
2025-03-10 15:02:54 +00:00
// function compressfile(data: string, notice?: boolean, settings?: compressorSettingsData) {
// if (notice) new Notice("Started", 3000)
// const comp = deflate(data)
// let inf = inflate(comp)
// if (inf && typeof (inf) != "string" && new TextDecoder().decode(inf) != data) {
// if (notice) new Notice("Revert is corrupted.", 3000)
// throw new Error("Revert not same,")
// }
// if (comp) {
// if (notice) new Notice("MEST", 3000)
// let bb = new BB()
// comp.forEach((byte: Number) => {
// // console.log(byte)
// bb.WU(8,byte)
// })
// if (notice) new Notice("SWAP", 3000)
// // let linkblk = ""
// // ;[...data.matchAll(/\[\[[^\]]*\]\]/gm)].forEach((match) => {
// // console.log(match)
// // linkblk += match[0]
// // })
// let encd = encodeSafe(bb.G())
// if (notice) new Notice("RLE", 3000)
// if (encd) {
// if (notice) new Notice("METADATA SET.", 3000)
// let content = `DATABLK` + encd
// if (content && content.length < data.length) {
// if (notice) new Notice("Success!")
// if (settings && settings.PrintResult) {
// NoticePool.push(new Notice(`Compression:\nratio:${((content.length / data.length) * 100).toFixed(1)}%`))
// }
// return content
// } else {
// if (notice) new Notice("File isnt smaller.")
// }
// } else {
// new Notice("Encoder failure.")
// }
// }
// }
// function decompressfile(data: string, notice?: boolean) {
// if (notice) new Notice("START", 3000)
// let bb = new BB()
// bb.F(decodeSafe(data.substring(data.indexOf("DATABLK") + 7)))
// let precont: number[] = []
// while (true) {
// let now = bb.RU(8)
// if (!isNaN(now)) {
// precont[precont.length] = now
// } else break
// }
// if (notice) new Notice("OUT", 3000)
// let cont = new Uint8Array(precont)
// if (notice) console.log(precont)
// if (notice) console.log(cont)
// if (notice) new Notice("SWAP", 3000)
// if (cont && cont.length > 0) {
// if (notice) new Notice("INF", 3000)
// let decomp = inflate(cont)
// if (notice) console.log(decomp)
// if (notice) new Notice("DONE, DECODE", 3000)
// if (typeof (decomp) == "object") {
// decomp = new TextDecoder().decode(decomp)
// if (notice) new Notice("Success!")
// return decomp
// } else {
// if (notice) new Notice("Failed.")
// }
// } else {
// if (notice) new Notice("Couldnt Decompress.")
// }
// }
2025-03-08 23:50:12 +00:00
function compressfile ( data : string , notice? : boolean , settings? : compressorSettingsData ) {
2025-03-10 15:02:54 +00:00
if ( notice ) new Notice ( "Started C" , 3000 )
let result : ( string | undefined ) = ""
try {
2025-03-27 05:21:13 +00:00
result = netcompress ( data , null , settings )
2025-03-10 15:02:54 +00:00
if ( notice ) new Notice ( "Done!" )
if ( settings && settings . PrintResult && result ) {
NoticePool . push ( new Notice ( ` Compression: \ nratio: ${ ( ( result . length / data . length ) * 100 ) . toFixed ( 1 ) } % ` ) )
2025-03-08 23:50:12 +00:00
}
2025-03-10 15:02:54 +00:00
} catch ( err ) {
new Notice ( err , 3000 )
throw new Error ( err )
2025-03-08 23:50:12 +00:00
}
2025-03-10 15:02:54 +00:00
if ( result == "" || ! result ) throw new Error ( "Empty" )
return result
2025-03-08 23:50:12 +00:00
}
function decompressfile ( data : string , notice? : boolean ) {
2025-03-10 15:02:54 +00:00
if ( notice ) new Notice ( "Started D" , 3000 )
let result : ( string | undefined ) = ""
try {
result = netdecompress ( data )
if ( notice ) new Notice ( "Done!" )
} catch ( err ) {
new Notice ( err , 3000 )
throw new Error ( err )
2025-03-08 23:50:12 +00:00
}
2025-03-10 15:02:54 +00:00
if ( result == "" || ! result ) throw new Error ( "Empty" )
return result
2025-03-08 23:50:12 +00:00
}
2025-03-11 00:27:05 +00:00
function checkCompFile ( data : string ) {
var state = false
try {
2025-03-25 03:55:52 +00:00
state = ( netdecompress ( data ) || "" ) . length > 0
2025-03-11 00:27:05 +00:00
} catch ( _ ) { }
return state
}
2025-03-08 23:50:12 +00:00
class compressorSettings extends PluginSettingTab {
plugin : compressorPlugin ;
constructor ( app : App , plugin : compressorPlugin ) {
super ( app , plugin ) ;
this . plugin = plugin ;
}
display ( ) : void {
const { containerEl } = this ;
containerEl . empty ( ) ;
new Setting ( containerEl )
. setName ( 'Print the compression results' )
. setDesc ( 'Everytime you\'re in a ctxt file and it gets saved itll then print out the compression ratio and byte save.' )
. addToggle ( bool = > bool
. setValue ( this . plugin . settings . PrintResult )
. onChange ( async ( value ) = > {
this . plugin . settings . PrintResult = value ;
await this . plugin . saveSettings ( ) ;
} ) ) ;
new Setting ( containerEl )
. setName ( 'Show the file storage size in the statusbar' )
. setDesc ( 'The amount of storage a file is taking up will be shown in the status bar of the open file.' )
. addToggle ( bool = > bool
. setValue ( this . plugin . settings . FileSize )
. onChange ( async ( value ) = > {
this . plugin . settings . FileSize = value ;
await this . plugin . saveSettings ( ) ;
} ) ) ;
2025-03-27 05:21:13 +00:00
// new Setting(containerEl)
// .setName('Pako')
// .setDesc('Use Pako to improve compression. May be intensive.')
// .addToggle(bool => bool
// .setValue(this.plugin.settings.PakoCompress)
// .onChange(async (value) => {
// this.plugin.settings.PakoCompress = value;
// await this.plugin.saveSettings();
// }));
2025-03-08 23:50:12 +00:00
new Setting ( containerEl )
. setName ( 'NOTICE:' )
. setDesc ( 'Unfortunetly the graph view will not maintain links. The graph cannot read the compressed data. Nor if the links are raw' )
2025-03-10 12:33:18 +00:00
new Setting ( containerEl )
. setName ( 'Debug' )
2025-03-25 03:55:52 +00:00
. setDesc ( 'Toasts all the events that are happening. Useful for debugging.' )
2025-03-10 12:33:18 +00:00
. addToggle ( bool = > bool
. setValue ( this . plugin . settings . Debug )
. onChange ( async ( value ) = > {
this . plugin . settings . Debug = value ;
await this . plugin . saveSettings ( ) ;
} ) ) ;
2025-03-25 03:55:52 +00:00
// new Setting(containerEl)
// .setName('-----Plugin Data Compression-----')
// .setDesc('Settings for compressing data that plugins save to vault')
//useless excelidraw uses whole offsets
// var ex_DP = new Setting(containerEl)
// .setName('Excalidraw: Decimal Precision')
// .setDesc(`How many decimals to have when saving numbers (${this.plugin.settings.ExcalidrawPrecision} DP)`)
// .addSlider(num => num
// .setValue(this.plugin.settings.ExcalidrawPrecision)
// .setLimits(0, 5, 1)
// .onChange(async (value) => {
// this.plugin.settings.ExcalidrawPrecision = value;
// ex_DP.setDesc(`How many decimals to have when saving numbers (${this.plugin.settings.ExcalidrawPrecision} DP) ${(this.plugin.settings.ExcalidrawPrecision==0)?"NO DECIMALS(VERY BAD)":""}`)
// await this.plugin.saveSettings();
// }));
2025-03-08 23:50:12 +00:00
}
}