2024-06-13 21:01:37 +03:00
/ *
THIS IS A GENERATED / BUNDLED FILE BY ESBUILD
if you want to view the source , please visit the github repository of this plugin
* /
var _ _create = Object . create ;
var _ _defProp = Object . defineProperty ;
var _ _getOwnPropDesc = Object . getOwnPropertyDescriptor ;
var _ _getOwnPropNames = Object . getOwnPropertyNames ;
var _ _getProtoOf = Object . getPrototypeOf ;
var _ _hasOwnProp = Object . prototype . hasOwnProperty ;
var _ _commonJS = ( cb , mod ) => function _ _require ( ) {
return mod || ( 0 , cb [ _ _getOwnPropNames ( cb ) [ 0 ] ] ) ( ( mod = { exports : { } } ) . exports , mod ) , mod . exports ;
} ;
var _ _export = ( target , all ) => {
for ( var name in all )
_ _defProp ( target , name , { get : all [ name ] , enumerable : true } ) ;
} ;
var _ _copyProps = ( to , from , except , desc ) => {
if ( from && typeof from === "object" || typeof from === "function" ) {
for ( let key of _ _getOwnPropNames ( from ) )
if ( ! _ _hasOwnProp . call ( to , key ) && key !== except )
_ _defProp ( to , key , { get : ( ) => from [ key ] , enumerable : ! ( desc = _ _getOwnPropDesc ( from , key ) ) || desc . enumerable } ) ;
}
return to ;
} ;
var _ _toESM = ( mod , isNodeMode , target ) => ( target = mod != null ? _ _create ( _ _getProtoOf ( mod ) ) : { } , _ _copyProps ( isNodeMode || ! mod || ! mod . _ _esModule ? _ _defProp ( target , "default" , { value : mod , enumerable : true } ) : target , mod ) ) ;
var _ _toCommonJS = ( mod ) => _ _copyProps ( _ _defProp ( { } , "__esModule" , { value : true } ) , mod ) ;
// node_modules/dompurify/dist/purify.js
var require _purify = _ _commonJS ( {
"node_modules/dompurify/dist/purify.js" ( exports , module2 ) {
( function ( global , factory ) {
typeof exports === "object" && typeof module2 !== "undefined" ? module2 . exports = factory ( ) : typeof define === "function" && define . amd ? define ( factory ) : ( global = typeof globalThis !== "undefined" ? globalThis : global || self , global . DOMPurify = factory ( ) ) ;
} ) ( exports , function ( ) {
"use strict" ;
const {
entries ,
setPrototypeOf ,
isFrozen ,
getPrototypeOf ,
getOwnPropertyDescriptor
} = Object ;
let {
freeze ,
seal ,
create
} = Object ;
let {
apply ,
construct
} = typeof Reflect !== "undefined" && Reflect ;
if ( ! freeze ) {
freeze = function freeze2 ( x ) {
return x ;
} ;
}
if ( ! seal ) {
seal = function seal2 ( x ) {
return x ;
} ;
}
if ( ! apply ) {
apply = function apply2 ( fun , thisValue , args ) {
return fun . apply ( thisValue , args ) ;
} ;
}
if ( ! construct ) {
construct = function construct2 ( Func , args ) {
return new Func ( ... args ) ;
} ;
}
const arrayForEach = unapply ( Array . prototype . forEach ) ;
const arrayPop = unapply ( Array . prototype . pop ) ;
const arrayPush = unapply ( Array . prototype . push ) ;
const stringToLowerCase = unapply ( String . prototype . toLowerCase ) ;
const stringToString = unapply ( String . prototype . toString ) ;
const stringMatch = unapply ( String . prototype . match ) ;
const stringReplace = unapply ( String . prototype . replace ) ;
const stringIndexOf = unapply ( String . prototype . indexOf ) ;
const stringTrim = unapply ( String . prototype . trim ) ;
const objectHasOwnProperty = unapply ( Object . prototype . hasOwnProperty ) ;
const regExpTest = unapply ( RegExp . prototype . test ) ;
const typeErrorCreate = unconstruct ( TypeError ) ;
function unapply ( func ) {
return function ( thisArg ) {
for ( var _len = arguments . length , args = new Array ( _len > 1 ? _len - 1 : 0 ) , _key = 1 ; _key < _len ; _key ++ ) {
args [ _key - 1 ] = arguments [ _key ] ;
}
return apply ( func , thisArg , args ) ;
} ;
}
function unconstruct ( func ) {
return function ( ) {
for ( var _len2 = arguments . length , args = new Array ( _len2 ) , _key2 = 0 ; _key2 < _len2 ; _key2 ++ ) {
args [ _key2 ] = arguments [ _key2 ] ;
}
return construct ( func , args ) ;
} ;
}
function addToSet ( set , array ) {
let transformCaseFunc = arguments . length > 2 && arguments [ 2 ] !== void 0 ? arguments [ 2 ] : stringToLowerCase ;
if ( setPrototypeOf ) {
setPrototypeOf ( set , null ) ;
}
let l = array . length ;
while ( l -- ) {
let element = array [ l ] ;
if ( typeof element === "string" ) {
const lcElement = transformCaseFunc ( element ) ;
if ( lcElement !== element ) {
if ( ! isFrozen ( array ) ) {
array [ l ] = lcElement ;
}
element = lcElement ;
}
}
set [ element ] = true ;
}
return set ;
}
function cleanArray ( array ) {
for ( let index = 0 ; index < array . length ; index ++ ) {
const isPropertyExist = objectHasOwnProperty ( array , index ) ;
if ( ! isPropertyExist ) {
array [ index ] = null ;
}
}
return array ;
}
function clone ( object ) {
const newObject = create ( null ) ;
for ( const [ property , value ] of entries ( object ) ) {
const isPropertyExist = objectHasOwnProperty ( object , property ) ;
if ( isPropertyExist ) {
if ( Array . isArray ( value ) ) {
newObject [ property ] = cleanArray ( value ) ;
} else if ( value && typeof value === "object" && value . constructor === Object ) {
newObject [ property ] = clone ( value ) ;
} else {
newObject [ property ] = value ;
}
}
}
return newObject ;
}
function lookupGetter ( object , prop ) {
while ( object !== null ) {
const desc = getOwnPropertyDescriptor ( object , prop ) ;
if ( desc ) {
if ( desc . get ) {
return unapply ( desc . get ) ;
}
if ( typeof desc . value === "function" ) {
return unapply ( desc . value ) ;
}
}
object = getPrototypeOf ( object ) ;
}
function fallbackValue ( ) {
return null ;
}
return fallbackValue ;
}
const html$1 = freeze ( [ "a" , "abbr" , "acronym" , "address" , "area" , "article" , "aside" , "audio" , "b" , "bdi" , "bdo" , "big" , "blink" , "blockquote" , "body" , "br" , "button" , "canvas" , "caption" , "center" , "cite" , "code" , "col" , "colgroup" , "content" , "data" , "datalist" , "dd" , "decorator" , "del" , "details" , "dfn" , "dialog" , "dir" , "div" , "dl" , "dt" , "element" , "em" , "fieldset" , "figcaption" , "figure" , "font" , "footer" , "form" , "h1" , "h2" , "h3" , "h4" , "h5" , "h6" , "head" , "header" , "hgroup" , "hr" , "html" , "i" , "img" , "input" , "ins" , "kbd" , "label" , "legend" , "li" , "main" , "map" , "mark" , "marquee" , "menu" , "menuitem" , "meter" , "nav" , "nobr" , "ol" , "optgroup" , "option" , "output" , "p" , "picture" , "pre" , "progress" , "q" , "rp" , "rt" , "ruby" , "s" , "samp" , "section" , "select" , "shadow" , "small" , "source" , "spacer" , "span" , "strike" , "strong" , "style" , "sub" , "summary" , "sup" , "table" , "tbody" , "td" , "template" , "textarea" , "tfoot" , "th" , "thead" , "time" , "tr" , "track" , "tt" , "u" , "ul" , "var" , "video" , "wbr" ] ) ;
const svg$1 = freeze ( [ "svg" , "a" , "altglyph" , "altglyphdef" , "altglyphitem" , "animatecolor" , "animatemotion" , "animatetransform" , "circle" , "clippath" , "defs" , "desc" , "ellipse" , "filter" , "font" , "g" , "glyph" , "glyphref" , "hkern" , "image" , "line" , "lineargradient" , "marker" , "mask" , "metadata" , "mpath" , "path" , "pattern" , "polygon" , "polyline" , "radialgradient" , "rect" , "stop" , "style" , "switch" , "symbol" , "text" , "textpath" , "title" , "tref" , "tspan" , "view" , "vkern" ] ) ;
const svgFilters = freeze ( [ "feBlend" , "feColorMatrix" , "feComponentTransfer" , "feComposite" , "feConvolveMatrix" , "feDiffuseLighting" , "feDisplacementMap" , "feDistantLight" , "feDropShadow" , "feFlood" , "feFuncA" , "feFuncB" , "feFuncG" , "feFuncR" , "feGaussianBlur" , "feImage" , "feMerge" , "feMergeNode" , "feMorphology" , "feOffset" , "fePointLight" , "feSpecularLighting" , "feSpotLight" , "feTile" , "feTurbulence" ] ) ;
const svgDisallowed = freeze ( [ "animate" , "color-profile" , "cursor" , "discard" , "font-face" , "font-face-format" , "font-face-name" , "font-face-src" , "font-face-uri" , "foreignobject" , "hatch" , "hatchpath" , "mesh" , "meshgradient" , "meshpatch" , "meshrow" , "missing-glyph" , "script" , "set" , "solidcolor" , "unknown" , "use" ] ) ;
const mathMl$1 = freeze ( [ "math" , "menclose" , "merror" , "mfenced" , "mfrac" , "mglyph" , "mi" , "mlabeledtr" , "mmultiscripts" , "mn" , "mo" , "mover" , "mpadded" , "mphantom" , "mroot" , "mrow" , "ms" , "mspace" , "msqrt" , "mstyle" , "msub" , "msup" , "msubsup" , "mtable" , "mtd" , "mtext" , "mtr" , "munder" , "munderover" , "mprescripts" ] ) ;
const mathMlDisallowed = freeze ( [ "maction" , "maligngroup" , "malignmark" , "mlongdiv" , "mscarries" , "mscarry" , "msgroup" , "mstack" , "msline" , "msrow" , "semantics" , "annotation" , "annotation-xml" , "mprescripts" , "none" ] ) ;
const text = freeze ( [ "#text" ] ) ;
const html = freeze ( [ "accept" , "action" , "align" , "alt" , "autocapitalize" , "autocomplete" , "autopictureinpicture" , "autoplay" , "background" , "bgcolor" , "border" , "capture" , "cellpadding" , "cellspacing" , "checked" , "cite" , "class" , "clear" , "color" , "cols" , "colspan" , "controls" , "controlslist" , "coords" , "crossorigin" , "datetime" , "decoding" , "default" , "dir" , "disabled" , "disablepictureinpicture" , "disableremoteplayback" , "download" , "draggable" , "enctype" , "enterkeyhint" , "face" , "for" , "headers" , "height" , "hidden" , "high" , "href" , "hreflang" , "id" , "inputmode" , "integrity" , "ismap" , "kind" , "label" , "lang" , "list" , "loading" , "loop" , "low" , "max" , "maxlength" , "media" , "method" , "min" , "minlength" , "multiple" , "muted" , "name" , "nonce" , "noshade" , "novalidate" , "nowrap" , "open" , "optimum" , "pattern" , "placeholder" , "playsinline" , "popover" , "popovertarget" , "popovertargetaction" , "poster" , "preload" , "pubdate" , "radiogroup" , "readonly" , "rel" , "required" , "rev" , "reversed" , "role" , "rows" , "rowspan" , "spellcheck" , "scope" , "selected" , "shape" , "size" , "sizes" , "span" , "srclang" , "start" , "src" , "srcset" , "step" , "style" , "summary" , "tabindex" , "title" , "translate" , "type" , "usemap" , "valign" , "value" , "width" , "wrap" , "xmlns" , "slot" ] ) ;
const svg = freeze ( [ "accent-height" , "accumulate" , "additive" , "alignment-baseline" , "ascent" , "attributename" , "attributetype" , "azimuth" , "basefrequency" , "baseline-shift" , "begin" , "bias" , "by" , "class" , "clip" , "clippathunits" , "clip-path" , "clip-rule" , "color" , "color-interpolation" , "color-interpolation-filters" , "color-profile" , "color-rendering" , "cx" , "cy" , "d" , "dx" , "dy" , "diffuseconstant" , "direction" , "display" , "divisor" , "dur" , "edgemode" , "elevation" , "end" , "fill" , "fill-opacity" , "fill-rule" , "filter" , "filterunits" , "flood-color" , "flood-opacity" , "font-family" , "font-size" , "font-size-adjust" , "font-stretch" , "font-style" , "font-variant" , "font-weight" , "fx" , "fy" , "g1" , "g2" , "glyph-name" , "glyphref" , "gradientunits" , "gradienttransform" , "height" , "href" , "id" , "image-rendering" , "in" , "in2" , "k" , "k1" , "k2" , "k3" , "k4" , "kerning" , "keypoints" , "keysplines" , "keytimes" , "lang" , "lengthadjust" , "letter-spacing" , "kernelmatrix" , "kernelunitlength" , "lighting-color" , "local" , "marker-end" , "marker-mid" , "marker-start" , "markerheight" , "markerunits" , "markerwidth" , "maskcontentunits" , "maskunits" , "max" , "mask" , "media" , "method" , "mode" , "min" , "name" , "numoctaves" , "offset" , "operator" , "opacity" , "order" , "orient" , "orientation" , "origin" , "overflow" , "paint-order" , "path" , "pathlength" , "patterncontentunits" , "patterntransform" , "patternunits" , "points" , "preservealpha" , "preserveaspectratio" , "primitiveunits" , "r" , "rx" , "ry" , "radius" , "refx" , "refy" , "repeatcount" , "repeatdur" , "restart" , "result" , "rotate" , "scale" , "seed" , "shape-rendering" , "specularconstant" , "specularexponent" , "spreadmethod" , "startoffset" , "stddeviation" , "stitchtiles" , "stop-color" , "stop-opacity" , "stroke-dasharray" , "stroke-dashoffset" , "stroke-linecap" , "stroke-linejoin" , "stroke-miterlimit" , "stroke-opacity" , "stroke" , "stroke-width" , "style" , "surfacescale" , "systemlanguage" , "tabindex" , "targetx" , "targety" , "transform" , "transform-origin" , "text-anchor" , "text-decoration" , "text-rendering" , "textlength" , "type" , "u1" , "u2" , "unicode" , "values" , "viewbox" , "visibility" , "version" , "vert-adv-y" , "vert-origin-x" , "vert-origin-y" , "width" , "word-spacing" , "wrap" , "writing-mode" , "xchannelselector" , "ychannelselector" , "x" , "x1" , "x2" , "xmlns" , "y" , "y1" , "y2" , "z" , "zoomandpan" ] ) ;
const mathMl = freeze ( [ "accent" , "accentunder" , "align" , "bevelled" , "close" , "columnsalign" , "columnlines" , "columnspan" , "denomalign" , "depth" , "dir" , "display" , "displaystyle" , "encoding" , "fence" , "frame" , "height" , "href" , "id" , "largeop" , "length" , "linethickness" , "lspace" , "lquote" , "mathbackground" , "mathcolor" , "mathsize" , "mathvariant" , "maxsize" , "minsize" , "movablelimits" , "notation" , "numalign" , "open" , "rowalign" , "rowlines" , "rowspacing" , "rowspan" , "rspace" , "rquote" , "scriptlevel" , "scriptminsize" , "scriptsizemultiplier" , "selection" , "separator" , "separators" , "stretchy" , "subscriptshift" , "supscriptshift" , "symmetric" , "voffset" , "width" , "xmlns" ] ) ;
const xml = freeze ( [ "xlink:href" , "xml:id" , "xlink:title" , "xml:space" , "xmlns:xlink" ] ) ;
const MUSTACHE _EXPR = seal ( /\{\{[\w\W]*|[\w\W]*\}\}/gm ) ;
const ERB _EXPR = seal ( /<%[\w\W]*|[\w\W]*%>/gm ) ;
const TMPLIT _EXPR = seal ( /\${[\w\W]*}/gm ) ;
const DATA _ATTR = seal ( /^data-[\-\w.\u00B7-\uFFFF]/ ) ;
const ARIA _ATTR = seal ( /^aria-[\-\w]+$/ ) ;
const IS _ALLOWED _URI = seal ( /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i ) ;
const IS _SCRIPT _OR _DATA = seal ( /^(?:\w+script|data):/i ) ;
const ATTR _WHITESPACE = seal ( /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g ) ;
const DOCTYPE _NAME = seal ( /^html$/i ) ;
const CUSTOM _ELEMENT = seal ( /^[a-z][.\w]*(-[.\w]+)+$/i ) ;
var EXPRESSIONS = /* @__PURE__ */ Object . freeze ( {
_ _proto _ _ : null ,
MUSTACHE _EXPR ,
ERB _EXPR ,
TMPLIT _EXPR ,
DATA _ATTR ,
ARIA _ATTR ,
IS _ALLOWED _URI ,
IS _SCRIPT _OR _DATA ,
ATTR _WHITESPACE ,
DOCTYPE _NAME ,
CUSTOM _ELEMENT
} ) ;
const NODE _TYPE = {
element : 1 ,
attribute : 2 ,
text : 3 ,
cdataSection : 4 ,
entityReference : 5 ,
entityNode : 6 ,
progressingInstruction : 7 ,
comment : 8 ,
document : 9 ,
documentType : 10 ,
documentFragment : 11 ,
notation : 12
} ;
const getGlobal = function getGlobal2 ( ) {
return typeof window === "undefined" ? null : window ;
} ;
const _createTrustedTypesPolicy = function _createTrustedTypesPolicy2 ( trustedTypes , purifyHostElement ) {
if ( typeof trustedTypes !== "object" || typeof trustedTypes . createPolicy !== "function" ) {
return null ;
}
let suffix = null ;
const ATTR _NAME = "data-tt-policy-suffix" ;
if ( purifyHostElement && purifyHostElement . hasAttribute ( ATTR _NAME ) ) {
suffix = purifyHostElement . getAttribute ( ATTR _NAME ) ;
}
const policyName = "dompurify" + ( suffix ? "#" + suffix : "" ) ;
try {
return trustedTypes . createPolicy ( policyName , {
createHTML ( html2 ) {
return html2 ;
} ,
createScriptURL ( scriptUrl ) {
return scriptUrl ;
}
} ) ;
} catch ( _ ) {
console . warn ( "TrustedTypes policy " + policyName + " could not be created." ) ;
return null ;
}
} ;
function createDOMPurify ( ) {
let window2 = arguments . length > 0 && arguments [ 0 ] !== void 0 ? arguments [ 0 ] : getGlobal ( ) ;
const DOMPurify2 = ( root ) => createDOMPurify ( root ) ;
2024-07-13 19:25:17 +03:00
DOMPurify2 . version = "3.1.5" ;
2024-06-13 21:01:37 +03:00
DOMPurify2 . removed = [ ] ;
if ( ! window2 || ! window2 . document || window2 . document . nodeType !== NODE _TYPE . document ) {
DOMPurify2 . isSupported = false ;
return DOMPurify2 ;
}
let {
document : document2
} = window2 ;
const originalDocument = document2 ;
const currentScript = originalDocument . currentScript ;
const {
DocumentFragment ,
HTMLTemplateElement ,
Node ,
Element ,
NodeFilter ,
NamedNodeMap = window2 . NamedNodeMap || window2 . MozNamedAttrMap ,
HTMLFormElement ,
DOMParser ,
trustedTypes
} = window2 ;
const ElementPrototype = Element . prototype ;
const cloneNode = lookupGetter ( ElementPrototype , "cloneNode" ) ;
const getNextSibling = lookupGetter ( ElementPrototype , "nextSibling" ) ;
const getChildNodes = lookupGetter ( ElementPrototype , "childNodes" ) ;
const getParentNode = lookupGetter ( ElementPrototype , "parentNode" ) ;
if ( typeof HTMLTemplateElement === "function" ) {
const template = document2 . createElement ( "template" ) ;
if ( template . content && template . content . ownerDocument ) {
document2 = template . content . ownerDocument ;
}
}
let trustedTypesPolicy ;
let emptyHTML = "" ;
const {
implementation ,
createNodeIterator ,
createDocumentFragment ,
getElementsByTagName
} = document2 ;
const {
importNode
} = originalDocument ;
let hooks = { } ;
DOMPurify2 . isSupported = typeof entries === "function" && typeof getParentNode === "function" && implementation && implementation . createHTMLDocument !== void 0 ;
const {
MUSTACHE _EXPR : MUSTACHE _EXPR2 ,
ERB _EXPR : ERB _EXPR2 ,
TMPLIT _EXPR : TMPLIT _EXPR2 ,
DATA _ATTR : DATA _ATTR2 ,
ARIA _ATTR : ARIA _ATTR2 ,
IS _SCRIPT _OR _DATA : IS _SCRIPT _OR _DATA2 ,
ATTR _WHITESPACE : ATTR _WHITESPACE2 ,
CUSTOM _ELEMENT : CUSTOM _ELEMENT2
} = EXPRESSIONS ;
let {
IS _ALLOWED _URI : IS _ALLOWED _URI$1
} = EXPRESSIONS ;
let ALLOWED _TAGS = null ;
const DEFAULT _ALLOWED _TAGS = addToSet ( { } , [ ... html$1 , ... svg$1 , ... svgFilters , ... mathMl$1 , ... text ] ) ;
let ALLOWED _ATTR = null ;
const DEFAULT _ALLOWED _ATTR = addToSet ( { } , [ ... html , ... svg , ... mathMl , ... xml ] ) ;
let CUSTOM _ELEMENT _HANDLING = Object . seal ( create ( null , {
tagNameCheck : {
writable : true ,
configurable : false ,
enumerable : true ,
value : null
} ,
attributeNameCheck : {
writable : true ,
configurable : false ,
enumerable : true ,
value : null
} ,
allowCustomizedBuiltInElements : {
writable : true ,
configurable : false ,
enumerable : true ,
value : false
}
} ) ) ;
let FORBID _TAGS = null ;
let FORBID _ATTR = null ;
let ALLOW _ARIA _ATTR = true ;
let ALLOW _DATA _ATTR = true ;
let ALLOW _UNKNOWN _PROTOCOLS = false ;
let ALLOW _SELF _CLOSE _IN _ATTR = true ;
let SAFE _FOR _TEMPLATES = false ;
let SAFE _FOR _XML = true ;
let WHOLE _DOCUMENT = false ;
let SET _CONFIG = false ;
let FORCE _BODY = false ;
let RETURN _DOM = false ;
let RETURN _DOM _FRAGMENT = false ;
let RETURN _TRUSTED _TYPE = false ;
let SANITIZE _DOM = true ;
let SANITIZE _NAMED _PROPS = false ;
const SANITIZE _NAMED _PROPS _PREFIX = "user-content-" ;
let KEEP _CONTENT = true ;
let IN _PLACE = false ;
let USE _PROFILES = { } ;
let FORBID _CONTENTS = null ;
const DEFAULT _FORBID _CONTENTS = addToSet ( { } , [ "annotation-xml" , "audio" , "colgroup" , "desc" , "foreignobject" , "head" , "iframe" , "math" , "mi" , "mn" , "mo" , "ms" , "mtext" , "noembed" , "noframes" , "noscript" , "plaintext" , "script" , "style" , "svg" , "template" , "thead" , "title" , "video" , "xmp" ] ) ;
let DATA _URI _TAGS = null ;
const DEFAULT _DATA _URI _TAGS = addToSet ( { } , [ "audio" , "video" , "img" , "source" , "image" , "track" ] ) ;
let URI _SAFE _ATTRIBUTES = null ;
const DEFAULT _URI _SAFE _ATTRIBUTES = addToSet ( { } , [ "alt" , "class" , "for" , "id" , "label" , "name" , "pattern" , "placeholder" , "role" , "summary" , "title" , "value" , "style" , "xmlns" ] ) ;
const MATHML _NAMESPACE = "http://www.w3.org/1998/Math/MathML" ;
const SVG _NAMESPACE = "http://www.w3.org/2000/svg" ;
const HTML _NAMESPACE = "http://www.w3.org/1999/xhtml" ;
let NAMESPACE = HTML _NAMESPACE ;
let IS _EMPTY _INPUT = false ;
let ALLOWED _NAMESPACES = null ;
const DEFAULT _ALLOWED _NAMESPACES = addToSet ( { } , [ MATHML _NAMESPACE , SVG _NAMESPACE , HTML _NAMESPACE ] , stringToString ) ;
let PARSER _MEDIA _TYPE = null ;
const SUPPORTED _PARSER _MEDIA _TYPES = [ "application/xhtml+xml" , "text/html" ] ;
const DEFAULT _PARSER _MEDIA _TYPE = "text/html" ;
let transformCaseFunc = null ;
let CONFIG = null ;
const formElement = document2 . createElement ( "form" ) ;
const isRegexOrFunction = function isRegexOrFunction2 ( testValue ) {
return testValue instanceof RegExp || testValue instanceof Function ;
} ;
const _parseConfig = function _parseConfig2 ( ) {
let cfg = arguments . length > 0 && arguments [ 0 ] !== void 0 ? arguments [ 0 ] : { } ;
if ( CONFIG && CONFIG === cfg ) {
return ;
}
if ( ! cfg || typeof cfg !== "object" ) {
cfg = { } ;
}
cfg = clone ( cfg ) ;
PARSER _MEDIA _TYPE = SUPPORTED _PARSER _MEDIA _TYPES . indexOf ( cfg . PARSER _MEDIA _TYPE ) === - 1 ? DEFAULT _PARSER _MEDIA _TYPE : cfg . PARSER _MEDIA _TYPE ;
transformCaseFunc = PARSER _MEDIA _TYPE === "application/xhtml+xml" ? stringToString : stringToLowerCase ;
ALLOWED _TAGS = objectHasOwnProperty ( cfg , "ALLOWED_TAGS" ) ? addToSet ( { } , cfg . ALLOWED _TAGS , transformCaseFunc ) : DEFAULT _ALLOWED _TAGS ;
ALLOWED _ATTR = objectHasOwnProperty ( cfg , "ALLOWED_ATTR" ) ? addToSet ( { } , cfg . ALLOWED _ATTR , transformCaseFunc ) : DEFAULT _ALLOWED _ATTR ;
ALLOWED _NAMESPACES = objectHasOwnProperty ( cfg , "ALLOWED_NAMESPACES" ) ? addToSet ( { } , cfg . ALLOWED _NAMESPACES , stringToString ) : DEFAULT _ALLOWED _NAMESPACES ;
URI _SAFE _ATTRIBUTES = objectHasOwnProperty ( cfg , "ADD_URI_SAFE_ATTR" ) ? addToSet ( clone ( DEFAULT _URI _SAFE _ATTRIBUTES ) , cfg . ADD _URI _SAFE _ATTR , transformCaseFunc ) : DEFAULT _URI _SAFE _ATTRIBUTES ;
DATA _URI _TAGS = objectHasOwnProperty ( cfg , "ADD_DATA_URI_TAGS" ) ? addToSet ( clone ( DEFAULT _DATA _URI _TAGS ) , cfg . ADD _DATA _URI _TAGS , transformCaseFunc ) : DEFAULT _DATA _URI _TAGS ;
FORBID _CONTENTS = objectHasOwnProperty ( cfg , "FORBID_CONTENTS" ) ? addToSet ( { } , cfg . FORBID _CONTENTS , transformCaseFunc ) : DEFAULT _FORBID _CONTENTS ;
FORBID _TAGS = objectHasOwnProperty ( cfg , "FORBID_TAGS" ) ? addToSet ( { } , cfg . FORBID _TAGS , transformCaseFunc ) : { } ;
FORBID _ATTR = objectHasOwnProperty ( cfg , "FORBID_ATTR" ) ? addToSet ( { } , cfg . FORBID _ATTR , transformCaseFunc ) : { } ;
USE _PROFILES = objectHasOwnProperty ( cfg , "USE_PROFILES" ) ? cfg . USE _PROFILES : false ;
ALLOW _ARIA _ATTR = cfg . ALLOW _ARIA _ATTR !== false ;
ALLOW _DATA _ATTR = cfg . ALLOW _DATA _ATTR !== false ;
ALLOW _UNKNOWN _PROTOCOLS = cfg . ALLOW _UNKNOWN _PROTOCOLS || false ;
ALLOW _SELF _CLOSE _IN _ATTR = cfg . ALLOW _SELF _CLOSE _IN _ATTR !== false ;
SAFE _FOR _TEMPLATES = cfg . SAFE _FOR _TEMPLATES || false ;
SAFE _FOR _XML = cfg . SAFE _FOR _XML !== false ;
WHOLE _DOCUMENT = cfg . WHOLE _DOCUMENT || false ;
RETURN _DOM = cfg . RETURN _DOM || false ;
RETURN _DOM _FRAGMENT = cfg . RETURN _DOM _FRAGMENT || false ;
RETURN _TRUSTED _TYPE = cfg . RETURN _TRUSTED _TYPE || false ;
FORCE _BODY = cfg . FORCE _BODY || false ;
SANITIZE _DOM = cfg . SANITIZE _DOM !== false ;
SANITIZE _NAMED _PROPS = cfg . SANITIZE _NAMED _PROPS || false ;
KEEP _CONTENT = cfg . KEEP _CONTENT !== false ;
IN _PLACE = cfg . IN _PLACE || false ;
IS _ALLOWED _URI$1 = cfg . ALLOWED _URI _REGEXP || IS _ALLOWED _URI ;
NAMESPACE = cfg . NAMESPACE || HTML _NAMESPACE ;
CUSTOM _ELEMENT _HANDLING = cfg . CUSTOM _ELEMENT _HANDLING || { } ;
if ( cfg . CUSTOM _ELEMENT _HANDLING && isRegexOrFunction ( cfg . CUSTOM _ELEMENT _HANDLING . tagNameCheck ) ) {
CUSTOM _ELEMENT _HANDLING . tagNameCheck = cfg . CUSTOM _ELEMENT _HANDLING . tagNameCheck ;
}
if ( cfg . CUSTOM _ELEMENT _HANDLING && isRegexOrFunction ( cfg . CUSTOM _ELEMENT _HANDLING . attributeNameCheck ) ) {
CUSTOM _ELEMENT _HANDLING . attributeNameCheck = cfg . CUSTOM _ELEMENT _HANDLING . attributeNameCheck ;
}
if ( cfg . CUSTOM _ELEMENT _HANDLING && typeof cfg . CUSTOM _ELEMENT _HANDLING . allowCustomizedBuiltInElements === "boolean" ) {
CUSTOM _ELEMENT _HANDLING . allowCustomizedBuiltInElements = cfg . CUSTOM _ELEMENT _HANDLING . allowCustomizedBuiltInElements ;
}
if ( SAFE _FOR _TEMPLATES ) {
ALLOW _DATA _ATTR = false ;
}
if ( RETURN _DOM _FRAGMENT ) {
RETURN _DOM = true ;
}
if ( USE _PROFILES ) {
ALLOWED _TAGS = addToSet ( { } , text ) ;
ALLOWED _ATTR = [ ] ;
if ( USE _PROFILES . html === true ) {
addToSet ( ALLOWED _TAGS , html$1 ) ;
addToSet ( ALLOWED _ATTR , html ) ;
}
if ( USE _PROFILES . svg === true ) {
addToSet ( ALLOWED _TAGS , svg$1 ) ;
addToSet ( ALLOWED _ATTR , svg ) ;
addToSet ( ALLOWED _ATTR , xml ) ;
}
if ( USE _PROFILES . svgFilters === true ) {
addToSet ( ALLOWED _TAGS , svgFilters ) ;
addToSet ( ALLOWED _ATTR , svg ) ;
addToSet ( ALLOWED _ATTR , xml ) ;
}
if ( USE _PROFILES . mathMl === true ) {
addToSet ( ALLOWED _TAGS , mathMl$1 ) ;
addToSet ( ALLOWED _ATTR , mathMl ) ;
addToSet ( ALLOWED _ATTR , xml ) ;
}
}
if ( cfg . ADD _TAGS ) {
if ( ALLOWED _TAGS === DEFAULT _ALLOWED _TAGS ) {
ALLOWED _TAGS = clone ( ALLOWED _TAGS ) ;
}
addToSet ( ALLOWED _TAGS , cfg . ADD _TAGS , transformCaseFunc ) ;
}
if ( cfg . ADD _ATTR ) {
if ( ALLOWED _ATTR === DEFAULT _ALLOWED _ATTR ) {
ALLOWED _ATTR = clone ( ALLOWED _ATTR ) ;
}
addToSet ( ALLOWED _ATTR , cfg . ADD _ATTR , transformCaseFunc ) ;
}
if ( cfg . ADD _URI _SAFE _ATTR ) {
addToSet ( URI _SAFE _ATTRIBUTES , cfg . ADD _URI _SAFE _ATTR , transformCaseFunc ) ;
}
if ( cfg . FORBID _CONTENTS ) {
if ( FORBID _CONTENTS === DEFAULT _FORBID _CONTENTS ) {
FORBID _CONTENTS = clone ( FORBID _CONTENTS ) ;
}
addToSet ( FORBID _CONTENTS , cfg . FORBID _CONTENTS , transformCaseFunc ) ;
}
if ( KEEP _CONTENT ) {
ALLOWED _TAGS [ "#text" ] = true ;
}
if ( WHOLE _DOCUMENT ) {
addToSet ( ALLOWED _TAGS , [ "html" , "head" , "body" ] ) ;
}
if ( ALLOWED _TAGS . table ) {
addToSet ( ALLOWED _TAGS , [ "tbody" ] ) ;
delete FORBID _TAGS . tbody ;
}
if ( cfg . TRUSTED _TYPES _POLICY ) {
if ( typeof cfg . TRUSTED _TYPES _POLICY . createHTML !== "function" ) {
throw typeErrorCreate ( 'TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.' ) ;
}
if ( typeof cfg . TRUSTED _TYPES _POLICY . createScriptURL !== "function" ) {
throw typeErrorCreate ( 'TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.' ) ;
}
trustedTypesPolicy = cfg . TRUSTED _TYPES _POLICY ;
emptyHTML = trustedTypesPolicy . createHTML ( "" ) ;
} else {
if ( trustedTypesPolicy === void 0 ) {
trustedTypesPolicy = _createTrustedTypesPolicy ( trustedTypes , currentScript ) ;
}
if ( trustedTypesPolicy !== null && typeof emptyHTML === "string" ) {
emptyHTML = trustedTypesPolicy . createHTML ( "" ) ;
}
}
if ( freeze ) {
freeze ( cfg ) ;
}
CONFIG = cfg ;
} ;
const MATHML _TEXT _INTEGRATION _POINTS = addToSet ( { } , [ "mi" , "mo" , "mn" , "ms" , "mtext" ] ) ;
const HTML _INTEGRATION _POINTS = addToSet ( { } , [ "foreignobject" , "annotation-xml" ] ) ;
const COMMON _SVG _AND _HTML _ELEMENTS = addToSet ( { } , [ "title" , "style" , "font" , "a" , "script" ] ) ;
const ALL _SVG _TAGS = addToSet ( { } , [ ... svg$1 , ... svgFilters , ... svgDisallowed ] ) ;
const ALL _MATHML _TAGS = addToSet ( { } , [ ... mathMl$1 , ... mathMlDisallowed ] ) ;
const _checkValidNamespace = function _checkValidNamespace2 ( element ) {
let parent = getParentNode ( element ) ;
if ( ! parent || ! parent . tagName ) {
parent = {
namespaceURI : NAMESPACE ,
tagName : "template"
} ;
}
const tagName = stringToLowerCase ( element . tagName ) ;
const parentTagName = stringToLowerCase ( parent . tagName ) ;
if ( ! ALLOWED _NAMESPACES [ element . namespaceURI ] ) {
return false ;
}
if ( element . namespaceURI === SVG _NAMESPACE ) {
if ( parent . namespaceURI === HTML _NAMESPACE ) {
return tagName === "svg" ;
}
if ( parent . namespaceURI === MATHML _NAMESPACE ) {
return tagName === "svg" && ( parentTagName === "annotation-xml" || MATHML _TEXT _INTEGRATION _POINTS [ parentTagName ] ) ;
}
return Boolean ( ALL _SVG _TAGS [ tagName ] ) ;
}
if ( element . namespaceURI === MATHML _NAMESPACE ) {
if ( parent . namespaceURI === HTML _NAMESPACE ) {
return tagName === "math" ;
}
if ( parent . namespaceURI === SVG _NAMESPACE ) {
return tagName === "math" && HTML _INTEGRATION _POINTS [ parentTagName ] ;
}
return Boolean ( ALL _MATHML _TAGS [ tagName ] ) ;
}
if ( element . namespaceURI === HTML _NAMESPACE ) {
if ( parent . namespaceURI === SVG _NAMESPACE && ! HTML _INTEGRATION _POINTS [ parentTagName ] ) {
return false ;
}
if ( parent . namespaceURI === MATHML _NAMESPACE && ! MATHML _TEXT _INTEGRATION _POINTS [ parentTagName ] ) {
return false ;
}
return ! ALL _MATHML _TAGS [ tagName ] && ( COMMON _SVG _AND _HTML _ELEMENTS [ tagName ] || ! ALL _SVG _TAGS [ tagName ] ) ;
}
if ( PARSER _MEDIA _TYPE === "application/xhtml+xml" && ALLOWED _NAMESPACES [ element . namespaceURI ] ) {
return true ;
}
return false ;
} ;
const _forceRemove = function _forceRemove2 ( node ) {
arrayPush ( DOMPurify2 . removed , {
element : node
} ) ;
try {
node . parentNode . removeChild ( node ) ;
} catch ( _ ) {
node . remove ( ) ;
}
} ;
const _removeAttribute = function _removeAttribute2 ( name , node ) {
try {
arrayPush ( DOMPurify2 . removed , {
attribute : node . getAttributeNode ( name ) ,
from : node
} ) ;
} catch ( _ ) {
arrayPush ( DOMPurify2 . removed , {
attribute : null ,
from : node
} ) ;
}
node . removeAttribute ( name ) ;
if ( name === "is" && ! ALLOWED _ATTR [ name ] ) {
if ( RETURN _DOM || RETURN _DOM _FRAGMENT ) {
try {
_forceRemove ( node ) ;
} catch ( _ ) {
}
} else {
try {
node . setAttribute ( name , "" ) ;
} catch ( _ ) {
}
}
}
} ;
const _initDocument = function _initDocument2 ( dirty ) {
let doc = null ;
let leadingWhitespace = null ;
if ( FORCE _BODY ) {
dirty = "<remove></remove>" + dirty ;
} else {
const matches = stringMatch ( dirty , /^[\r\n\t ]+/ ) ;
leadingWhitespace = matches && matches [ 0 ] ;
}
if ( PARSER _MEDIA _TYPE === "application/xhtml+xml" && NAMESPACE === HTML _NAMESPACE ) {
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + "</body></html>" ;
}
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy . createHTML ( dirty ) : dirty ;
if ( NAMESPACE === HTML _NAMESPACE ) {
try {
doc = new DOMParser ( ) . parseFromString ( dirtyPayload , PARSER _MEDIA _TYPE ) ;
} catch ( _ ) {
}
}
if ( ! doc || ! doc . documentElement ) {
doc = implementation . createDocument ( NAMESPACE , "template" , null ) ;
try {
doc . documentElement . innerHTML = IS _EMPTY _INPUT ? emptyHTML : dirtyPayload ;
} catch ( _ ) {
}
}
const body = doc . body || doc . documentElement ;
if ( dirty && leadingWhitespace ) {
body . insertBefore ( document2 . createTextNode ( leadingWhitespace ) , body . childNodes [ 0 ] || null ) ;
}
if ( NAMESPACE === HTML _NAMESPACE ) {
return getElementsByTagName . call ( doc , WHOLE _DOCUMENT ? "html" : "body" ) [ 0 ] ;
}
return WHOLE _DOCUMENT ? doc . documentElement : body ;
} ;
const _createNodeIterator = function _createNodeIterator2 ( root ) {
return createNodeIterator . call ( root . ownerDocument || root , root , NodeFilter . SHOW _ELEMENT | NodeFilter . SHOW _COMMENT | NodeFilter . SHOW _TEXT | NodeFilter . SHOW _PROCESSING _INSTRUCTION | NodeFilter . SHOW _CDATA _SECTION , null ) ;
} ;
const _isClobbered = function _isClobbered2 ( elm ) {
2024-07-13 19:25:17 +03:00
return elm instanceof HTMLFormElement && ( typeof elm . nodeName !== "string" || typeof elm . textContent !== "string" || typeof elm . removeChild !== "function" || ! ( elm . attributes instanceof NamedNodeMap ) || typeof elm . removeAttribute !== "function" || typeof elm . setAttribute !== "function" || typeof elm . namespaceURI !== "string" || typeof elm . insertBefore !== "function" || typeof elm . hasChildNodes !== "function" ) ;
2024-06-13 21:01:37 +03:00
} ;
const _isNode = function _isNode2 ( object ) {
return typeof Node === "function" && object instanceof Node ;
} ;
const _executeHook = function _executeHook2 ( entryPoint , currentNode , data ) {
if ( ! hooks [ entryPoint ] ) {
return ;
}
arrayForEach ( hooks [ entryPoint ] , ( hook ) => {
hook . call ( DOMPurify2 , currentNode , data , CONFIG ) ;
} ) ;
} ;
const _sanitizeElements = function _sanitizeElements2 ( currentNode ) {
let content = null ;
_executeHook ( "beforeSanitizeElements" , currentNode , null ) ;
if ( _isClobbered ( currentNode ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
const tagName = transformCaseFunc ( currentNode . nodeName ) ;
_executeHook ( "uponSanitizeElement" , currentNode , {
tagName ,
allowedTags : ALLOWED _TAGS
} ) ;
if ( currentNode . hasChildNodes ( ) && ! _isNode ( currentNode . firstElementChild ) && regExpTest ( /<[/\w]/g , currentNode . innerHTML ) && regExpTest ( /<[/\w]/g , currentNode . textContent ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
if ( currentNode . nodeType === NODE _TYPE . progressingInstruction ) {
_forceRemove ( currentNode ) ;
return true ;
}
if ( SAFE _FOR _XML && currentNode . nodeType === NODE _TYPE . comment && regExpTest ( /<[/\w]/g , currentNode . data ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
if ( ! ALLOWED _TAGS [ tagName ] || FORBID _TAGS [ tagName ] ) {
if ( ! FORBID _TAGS [ tagName ] && _isBasicCustomElement ( tagName ) ) {
if ( CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof RegExp && regExpTest ( CUSTOM _ELEMENT _HANDLING . tagNameCheck , tagName ) ) {
return false ;
}
if ( CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof Function && CUSTOM _ELEMENT _HANDLING . tagNameCheck ( tagName ) ) {
return false ;
}
}
if ( KEEP _CONTENT && ! FORBID _CONTENTS [ tagName ] ) {
const parentNode = getParentNode ( currentNode ) || currentNode . parentNode ;
const childNodes = getChildNodes ( currentNode ) || currentNode . childNodes ;
if ( childNodes && parentNode ) {
const childCount = childNodes . length ;
for ( let i = childCount - 1 ; i >= 0 ; -- i ) {
const childClone = cloneNode ( childNodes [ i ] , true ) ;
childClone . _ _removalCount = ( currentNode . _ _removalCount || 0 ) + 1 ;
parentNode . insertBefore ( childClone , getNextSibling ( currentNode ) ) ;
}
}
}
_forceRemove ( currentNode ) ;
return true ;
}
if ( currentNode instanceof Element && ! _checkValidNamespace ( currentNode ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
if ( ( tagName === "noscript" || tagName === "noembed" || tagName === "noframes" ) && regExpTest ( /<\/no(script|embed|frames)/i , currentNode . innerHTML ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
if ( SAFE _FOR _TEMPLATES && currentNode . nodeType === NODE _TYPE . text ) {
content = currentNode . textContent ;
arrayForEach ( [ MUSTACHE _EXPR2 , ERB _EXPR2 , TMPLIT _EXPR2 ] , ( expr ) => {
content = stringReplace ( content , expr , " " ) ;
} ) ;
if ( currentNode . textContent !== content ) {
arrayPush ( DOMPurify2 . removed , {
element : currentNode . cloneNode ( )
} ) ;
currentNode . textContent = content ;
}
}
_executeHook ( "afterSanitizeElements" , currentNode , null ) ;
return false ;
} ;
const _isValidAttribute = function _isValidAttribute2 ( lcTag , lcName , value ) {
2024-07-13 19:25:17 +03:00
if ( SANITIZE _DOM && ( lcName === "id" || lcName === "name" ) && ( value in document2 || value in formElement ) ) {
2024-06-13 21:01:37 +03:00
return false ;
}
if ( ALLOW _DATA _ATTR && ! FORBID _ATTR [ lcName ] && regExpTest ( DATA _ATTR2 , lcName ) )
;
else if ( ALLOW _ARIA _ATTR && regExpTest ( ARIA _ATTR2 , lcName ) )
;
else if ( ! ALLOWED _ATTR [ lcName ] || FORBID _ATTR [ lcName ] ) {
if ( _isBasicCustomElement ( lcTag ) && ( CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof RegExp && regExpTest ( CUSTOM _ELEMENT _HANDLING . tagNameCheck , lcTag ) || CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof Function && CUSTOM _ELEMENT _HANDLING . tagNameCheck ( lcTag ) ) && ( CUSTOM _ELEMENT _HANDLING . attributeNameCheck instanceof RegExp && regExpTest ( CUSTOM _ELEMENT _HANDLING . attributeNameCheck , lcName ) || CUSTOM _ELEMENT _HANDLING . attributeNameCheck instanceof Function && CUSTOM _ELEMENT _HANDLING . attributeNameCheck ( lcName ) ) || lcName === "is" && CUSTOM _ELEMENT _HANDLING . allowCustomizedBuiltInElements && ( CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof RegExp && regExpTest ( CUSTOM _ELEMENT _HANDLING . tagNameCheck , value ) || CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof Function && CUSTOM _ELEMENT _HANDLING . tagNameCheck ( value ) ) )
;
else {
return false ;
}
} else if ( URI _SAFE _ATTRIBUTES [ lcName ] )
;
else if ( regExpTest ( IS _ALLOWED _URI$1 , stringReplace ( value , ATTR _WHITESPACE2 , "" ) ) )
;
else if ( ( lcName === "src" || lcName === "xlink:href" || lcName === "href" ) && lcTag !== "script" && stringIndexOf ( value , "data:" ) === 0 && DATA _URI _TAGS [ lcTag ] )
;
else if ( ALLOW _UNKNOWN _PROTOCOLS && ! regExpTest ( IS _SCRIPT _OR _DATA2 , stringReplace ( value , ATTR _WHITESPACE2 , "" ) ) )
;
else if ( value ) {
return false ;
} else
;
return true ;
} ;
const _isBasicCustomElement = function _isBasicCustomElement2 ( tagName ) {
return tagName !== "annotation-xml" && stringMatch ( tagName , CUSTOM _ELEMENT2 ) ;
} ;
const _sanitizeAttributes = function _sanitizeAttributes2 ( currentNode ) {
_executeHook ( "beforeSanitizeAttributes" , currentNode , null ) ;
const {
attributes
} = currentNode ;
if ( ! attributes ) {
return ;
}
const hookEvent = {
attrName : "" ,
attrValue : "" ,
keepAttr : true ,
allowedAttributes : ALLOWED _ATTR
} ;
let l = attributes . length ;
while ( l -- ) {
const attr = attributes [ l ] ;
const {
name ,
namespaceURI ,
value : attrValue
} = attr ;
const lcName = transformCaseFunc ( name ) ;
let value = name === "value" ? attrValue : stringTrim ( attrValue ) ;
hookEvent . attrName = lcName ;
hookEvent . attrValue = value ;
hookEvent . keepAttr = true ;
hookEvent . forceKeepAttr = void 0 ;
_executeHook ( "uponSanitizeAttribute" , currentNode , hookEvent ) ;
value = hookEvent . attrValue ;
if ( hookEvent . forceKeepAttr ) {
continue ;
}
_removeAttribute ( name , currentNode ) ;
if ( ! hookEvent . keepAttr ) {
continue ;
}
if ( ! ALLOW _SELF _CLOSE _IN _ATTR && regExpTest ( /\/>/i , value ) ) {
_removeAttribute ( name , currentNode ) ;
continue ;
}
if ( SAFE _FOR _XML && regExpTest ( /((--!?|])>)|<\/(style|title)/i , value ) ) {
_removeAttribute ( name , currentNode ) ;
continue ;
}
if ( SAFE _FOR _TEMPLATES ) {
arrayForEach ( [ MUSTACHE _EXPR2 , ERB _EXPR2 , TMPLIT _EXPR2 ] , ( expr ) => {
value = stringReplace ( value , expr , " " ) ;
} ) ;
}
const lcTag = transformCaseFunc ( currentNode . nodeName ) ;
if ( ! _isValidAttribute ( lcTag , lcName , value ) ) {
continue ;
}
if ( SANITIZE _NAMED _PROPS && ( lcName === "id" || lcName === "name" ) ) {
_removeAttribute ( name , currentNode ) ;
value = SANITIZE _NAMED _PROPS _PREFIX + value ;
}
if ( trustedTypesPolicy && typeof trustedTypes === "object" && typeof trustedTypes . getAttributeType === "function" ) {
if ( namespaceURI )
;
else {
switch ( trustedTypes . getAttributeType ( lcTag , lcName ) ) {
case "TrustedHTML" : {
value = trustedTypesPolicy . createHTML ( value ) ;
break ;
}
case "TrustedScriptURL" : {
value = trustedTypesPolicy . createScriptURL ( value ) ;
break ;
}
}
}
}
try {
if ( namespaceURI ) {
currentNode . setAttributeNS ( namespaceURI , name , value ) ;
} else {
currentNode . setAttribute ( name , value ) ;
}
if ( _isClobbered ( currentNode ) ) {
_forceRemove ( currentNode ) ;
} else {
arrayPop ( DOMPurify2 . removed ) ;
}
} catch ( _ ) {
}
}
_executeHook ( "afterSanitizeAttributes" , currentNode , null ) ;
} ;
const _sanitizeShadowDOM = function _sanitizeShadowDOM2 ( fragment ) {
let shadowNode = null ;
const shadowIterator = _createNodeIterator ( fragment ) ;
_executeHook ( "beforeSanitizeShadowDOM" , fragment , null ) ;
while ( shadowNode = shadowIterator . nextNode ( ) ) {
_executeHook ( "uponSanitizeShadowNode" , shadowNode , null ) ;
if ( _sanitizeElements ( shadowNode ) ) {
continue ;
}
if ( shadowNode . content instanceof DocumentFragment ) {
_sanitizeShadowDOM2 ( shadowNode . content ) ;
}
_sanitizeAttributes ( shadowNode ) ;
}
_executeHook ( "afterSanitizeShadowDOM" , fragment , null ) ;
} ;
DOMPurify2 . sanitize = function ( dirty ) {
let cfg = arguments . length > 1 && arguments [ 1 ] !== void 0 ? arguments [ 1 ] : { } ;
let body = null ;
let importedNode = null ;
let currentNode = null ;
let returnNode = null ;
IS _EMPTY _INPUT = ! dirty ;
if ( IS _EMPTY _INPUT ) {
dirty = "<!-->" ;
}
if ( typeof dirty !== "string" && ! _isNode ( dirty ) ) {
if ( typeof dirty . toString === "function" ) {
dirty = dirty . toString ( ) ;
if ( typeof dirty !== "string" ) {
throw typeErrorCreate ( "dirty is not a string, aborting" ) ;
}
} else {
throw typeErrorCreate ( "toString is not a function" ) ;
}
}
if ( ! DOMPurify2 . isSupported ) {
return dirty ;
}
if ( ! SET _CONFIG ) {
_parseConfig ( cfg ) ;
}
DOMPurify2 . removed = [ ] ;
if ( typeof dirty === "string" ) {
IN _PLACE = false ;
}
if ( IN _PLACE ) {
if ( dirty . nodeName ) {
const tagName = transformCaseFunc ( dirty . nodeName ) ;
if ( ! ALLOWED _TAGS [ tagName ] || FORBID _TAGS [ tagName ] ) {
throw typeErrorCreate ( "root node is forbidden and cannot be sanitized in-place" ) ;
}
}
} else if ( dirty instanceof Node ) {
body = _initDocument ( "<!---->" ) ;
importedNode = body . ownerDocument . importNode ( dirty , true ) ;
if ( importedNode . nodeType === NODE _TYPE . element && importedNode . nodeName === "BODY" ) {
body = importedNode ;
} else if ( importedNode . nodeName === "HTML" ) {
body = importedNode ;
} else {
body . appendChild ( importedNode ) ;
}
} else {
if ( ! RETURN _DOM && ! SAFE _FOR _TEMPLATES && ! WHOLE _DOCUMENT && dirty . indexOf ( "<" ) === - 1 ) {
return trustedTypesPolicy && RETURN _TRUSTED _TYPE ? trustedTypesPolicy . createHTML ( dirty ) : dirty ;
}
body = _initDocument ( dirty ) ;
if ( ! body ) {
return RETURN _DOM ? null : RETURN _TRUSTED _TYPE ? emptyHTML : "" ;
}
}
if ( body && FORCE _BODY ) {
_forceRemove ( body . firstChild ) ;
}
const nodeIterator = _createNodeIterator ( IN _PLACE ? dirty : body ) ;
while ( currentNode = nodeIterator . nextNode ( ) ) {
if ( _sanitizeElements ( currentNode ) ) {
continue ;
}
if ( currentNode . content instanceof DocumentFragment ) {
_sanitizeShadowDOM ( currentNode . content ) ;
}
_sanitizeAttributes ( currentNode ) ;
}
if ( IN _PLACE ) {
return dirty ;
}
if ( RETURN _DOM ) {
if ( RETURN _DOM _FRAGMENT ) {
returnNode = createDocumentFragment . call ( body . ownerDocument ) ;
while ( body . firstChild ) {
returnNode . appendChild ( body . firstChild ) ;
}
} else {
returnNode = body ;
}
if ( ALLOWED _ATTR . shadowroot || ALLOWED _ATTR . shadowrootmode ) {
returnNode = importNode . call ( originalDocument , returnNode , true ) ;
}
return returnNode ;
}
let serializedHTML = WHOLE _DOCUMENT ? body . outerHTML : body . innerHTML ;
if ( WHOLE _DOCUMENT && ALLOWED _TAGS [ "!doctype" ] && body . ownerDocument && body . ownerDocument . doctype && body . ownerDocument . doctype . name && regExpTest ( DOCTYPE _NAME , body . ownerDocument . doctype . name ) ) {
serializedHTML = "<!DOCTYPE " + body . ownerDocument . doctype . name + ">\n" + serializedHTML ;
}
if ( SAFE _FOR _TEMPLATES ) {
arrayForEach ( [ MUSTACHE _EXPR2 , ERB _EXPR2 , TMPLIT _EXPR2 ] , ( expr ) => {
serializedHTML = stringReplace ( serializedHTML , expr , " " ) ;
} ) ;
}
return trustedTypesPolicy && RETURN _TRUSTED _TYPE ? trustedTypesPolicy . createHTML ( serializedHTML ) : serializedHTML ;
} ;
DOMPurify2 . setConfig = function ( ) {
let cfg = arguments . length > 0 && arguments [ 0 ] !== void 0 ? arguments [ 0 ] : { } ;
_parseConfig ( cfg ) ;
SET _CONFIG = true ;
} ;
DOMPurify2 . clearConfig = function ( ) {
CONFIG = null ;
SET _CONFIG = false ;
} ;
DOMPurify2 . isValidAttribute = function ( tag , attr , value ) {
if ( ! CONFIG ) {
_parseConfig ( { } ) ;
}
const lcTag = transformCaseFunc ( tag ) ;
const lcName = transformCaseFunc ( attr ) ;
return _isValidAttribute ( lcTag , lcName , value ) ;
} ;
DOMPurify2 . addHook = function ( entryPoint , hookFunction ) {
if ( typeof hookFunction !== "function" ) {
return ;
}
hooks [ entryPoint ] = hooks [ entryPoint ] || [ ] ;
arrayPush ( hooks [ entryPoint ] , hookFunction ) ;
} ;
DOMPurify2 . removeHook = function ( entryPoint ) {
if ( hooks [ entryPoint ] ) {
return arrayPop ( hooks [ entryPoint ] ) ;
}
} ;
DOMPurify2 . removeHooks = function ( entryPoint ) {
if ( hooks [ entryPoint ] ) {
hooks [ entryPoint ] = [ ] ;
}
} ;
DOMPurify2 . removeAllHooks = function ( ) {
hooks = { } ;
} ;
return DOMPurify2 ;
}
var purify = createDOMPurify ( ) ;
return purify ;
} ) ;
}
} ) ;
// src/main.ts
var main _exports = { } ;
_ _export ( main _exports , {
default : ( ) => Khoj
} ) ;
module . exports = _ _toCommonJS ( main _exports ) ;
var import _obsidian6 = require ( "obsidian" ) ;
// src/settings.ts
var import _obsidian2 = require ( "obsidian" ) ;
// src/utils.ts
var import _obsidian = require ( "obsidian" ) ;
function fileExtensionToMimeType ( extension ) {
switch ( extension ) {
case "pdf" :
return "application/pdf" ;
case "png" :
return "image/png" ;
case "jpg" :
case "jpeg" :
return "image/jpeg" ;
case "md" :
case "markdown" :
return "text/markdown" ;
case "org" :
return "text/org" ;
default :
return "text/plain" ;
}
}
function filenameToMimeType ( filename ) {
switch ( filename . extension ) {
case "pdf" :
return "application/pdf" ;
case "png" :
return "image/png" ;
case "jpg" :
case "jpeg" :
return "image/jpeg" ;
case "md" :
case "markdown" :
return "text/markdown" ;
case "org" :
return "text/org" ;
default :
console . warn ( ` Unknown file type: ${ filename . extension } . Defaulting to text/plain. ` ) ;
return "text/plain" ;
}
}
2024-07-29 07:51:09 +03:00
var supportedImageFilesTypes = [ "png" , "jpg" , "jpeg" ] ;
var supportedBinaryFileTypes = [ "pdf" ] . concat ( supportedImageFilesTypes ) ;
var supportedFileTypes = [ "md" , "markdown" ] . concat ( supportedBinaryFileTypes ) ;
2024-06-13 21:01:37 +03:00
async function updateContentIndex ( vault , setting , lastSync , regenerate = false ) {
var _a ;
console . log ( ` Khoj: Updating Khoj content index... ` ) ;
2024-07-29 07:51:09 +03:00
const files = vault . getFiles ( ) . filter ( ( file ) => supportedFileTypes . includes ( file . extension ) ) ;
2024-06-13 21:01:37 +03:00
let countOfFilesToIndex = 0 ;
let countOfFilesToDelete = 0 ;
lastSync = lastSync . size > 0 ? lastSync : /* @__PURE__ */ new Map ( ) ;
const fileData = [ ] ;
for ( const file of files ) {
if ( ! regenerate && file . stat . mtime < ( ( _a = lastSync . get ( file ) ) != null ? _a : 0 ) ) {
continue ;
}
countOfFilesToIndex ++ ;
2024-07-29 07:51:09 +03:00
const encoding = supportedBinaryFileTypes . includes ( file . extension ) ? "binary" : "utf8" ;
2024-06-13 21:01:37 +03:00
const mimeType = fileExtensionToMimeType ( file . extension ) + ( encoding === "utf8" ? "; charset=UTF-8" : "" ) ;
const fileContent = encoding == "binary" ? await vault . readBinary ( file ) : await vault . read ( file ) ;
fileData . push ( { blob : new Blob ( [ fileContent ] , { type : mimeType } ) , path : file . path } ) ;
}
let filesToDelete = [ ] ;
for ( const lastSyncedFile of lastSync . keys ( ) ) {
if ( ! files . includes ( lastSyncedFile ) ) {
countOfFilesToDelete ++ ;
let fileObj = new Blob ( [ "" ] , { type : filenameToMimeType ( lastSyncedFile ) } ) ;
fileData . push ( { blob : fileObj , path : lastSyncedFile . path } ) ;
filesToDelete . push ( lastSyncedFile ) ;
}
}
let responses = [ ] ;
let error _message = null ;
for ( let i = 0 ; i < fileData . length ; i += 1e3 ) {
const filesGroup = fileData . slice ( i , i + 1e3 ) ;
const formData = new FormData ( ) ;
2024-08-09 08:39:12 +03:00
const method = regenerate ? "PUT" : "PATCH" ;
2024-06-13 21:01:37 +03:00
filesGroup . forEach ( ( fileItem ) => {
formData . append ( "files" , fileItem . blob , fileItem . path ) ;
} ) ;
2024-08-09 08:39:12 +03:00
const response = await fetch ( ` ${ setting . khojUrl } /api/content?client=obsidian ` , {
method ,
2024-06-13 21:01:37 +03:00
headers : {
"Authorization" : ` Bearer ${ setting . khojApiKey } `
} ,
body : formData
} ) ;
if ( ! response . ok ) {
if ( response . status === 429 ) {
error _message = ` \u 2757 \u FE0FFailed to sync your content with Khoj server. Requests were throttled. Upgrade your subscription or try again later. ` ;
break ;
} else if ( response . status === 404 ) {
error _message = ` \u 2757 \u FE0FCould not connect to Khoj server. Ensure you can connect to it. ` ;
break ;
} else {
error _message = ` \u 2757 \u FE0FFailed to sync your content with Khoj server. Raise issue on Khoj Discord or Github
Error : $ { response . statusText } ` ;
}
} else {
responses . push ( await response . text ( ) ) ;
}
}
files . filter ( ( file ) => responses . find ( ( response ) => response . includes ( file . path ) ) ) . reduce ( ( newSync , file ) => {
newSync . set ( file , new Date ( ) . getTime ( ) ) ;
return newSync ;
} , lastSync ) ;
filesToDelete . filter ( ( file ) => responses . find ( ( response ) => response . includes ( file . path ) ) ) . forEach ( ( file ) => lastSync . delete ( file ) ) ;
if ( error _message ) {
new import _obsidian . Notice ( error _message ) ;
} else {
console . log ( ` \u 2705 Refreshed Khoj content index. Updated: ${ countOfFilesToIndex } files, Deleted: ${ countOfFilesToDelete } files. ` ) ;
}
return lastSync ;
}
async function createNote ( name , newLeaf = false ) {
var _a , _b ;
try {
let pathPrefix ;
switch ( this . app . vault . getConfig ( "newFileLocation" ) ) {
case "current" :
pathPrefix = ( ( _b = ( _a = this . app . workspace . getActiveFile ( ) ) == null ? void 0 : _a . parent . path ) != null ? _b : "" ) + "/" ;
break ;
case "folder" :
pathPrefix = this . app . vault . getConfig ( "newFileFolderPath" ) + "/" ;
break ;
default :
pathPrefix = "" ;
break ;
}
await this . app . workspace . openLinkText ( ` ${ pathPrefix } ${ name } .md ` , "" , newLeaf ) ;
} catch ( e ) {
console . error ( "Khoj: Could not create note.\n" + e . message ) ;
throw e ;
}
}
async function createNoteAndCloseModal ( query , modal , opt ) {
try {
await createNote ( query , opt == null ? void 0 : opt . newLeaf ) ;
} catch ( e ) {
new import _obsidian . Notice ( e . message ) ;
return ;
}
modal . close ( ) ;
}
async function canConnectToBackend ( khojUrl , khojApiKey , showNotice = false ) {
let connectedToBackend = false ;
let userInfo = null ;
if ( ! ! khojUrl ) {
let headers = ! ! khojApiKey ? { "Authorization" : ` Bearer ${ khojApiKey } ` } : void 0 ;
try {
let response = await ( 0 , import _obsidian . request ) ( { url : ` ${ khojUrl } /api/v1/user ` , method : "GET" , headers } ) ;
connectedToBackend = true ;
userInfo = JSON . parse ( response ) ;
} catch ( error ) {
connectedToBackend = false ;
console . log ( ` Khoj connection error:
$ { error } ` );
}
;
}
let statusMessage = getBackendStatusMessage ( connectedToBackend , userInfo == null ? void 0 : userInfo . email , khojUrl , khojApiKey ) ;
if ( showNotice )
new import _obsidian . Notice ( statusMessage ) ;
return { connectedToBackend , statusMessage , userInfo } ;
}
function getBackendStatusMessage ( connectedToServer , userEmail , khojUrl , khojApiKey ) {
if ( ! khojApiKey && khojUrl === "https://app.khoj.dev" )
2024-08-09 08:39:12 +03:00
return ` \u {1F308} Welcome to Khoj! Get your API key from ${ khojUrl } /settings#clients and set it in the Khoj plugin settings on Obsidian ` ;
2024-06-13 21:01:37 +03:00
if ( ! connectedToServer )
return ` \u 2757 \u FE0FCould not connect to Khoj at ${ khojUrl } . Ensure your can access it ` ;
else if ( ! userEmail )
2024-08-09 08:39:12 +03:00
return ` \u 2705 Connected to Khoj. \u 2757 \u FE0FGet a valid API key from ${ khojUrl } /settings#clients to log in ` ;
2024-06-13 21:01:37 +03:00
else if ( userEmail === "default@example.com" )
return ` \u 2705 Signed in to Khoj ` ;
else
return ` \u 2705 Signed in to Khoj as ${ userEmail } ` ;
}
async function populateHeaderPane ( headerEl , setting ) {
let userInfo = null ;
try {
const { userInfo : extractedUserInfo } = await canConnectToBackend ( setting . khojUrl , setting . khojApiKey , false ) ;
userInfo = extractedUserInfo ;
} catch ( error ) {
console . error ( "\u2757\uFE0FCould not connect to Khoj" ) ;
}
const titleEl = headerEl . createDiv ( ) ;
titleEl . className = "khoj-logo" ;
titleEl . textContent = "KHOJ" ;
const nav = headerEl . createEl ( "nav" ) ;
nav . className = "khoj-nav" ;
const chatLink = nav . createEl ( "a" ) ;
chatLink . id = "chat-nav" ;
chatLink . className = "khoj-nav chat-nav" ;
const chatIcon = chatLink . createEl ( "span" ) ;
chatIcon . className = "khoj-nav-icon khoj-nav-icon-chat" ;
( 0 , import _obsidian . setIcon ) ( chatIcon , "khoj-chat" ) ;
const chatText = chatLink . createEl ( "span" ) ;
chatText . className = "khoj-nav-item-text" ;
chatText . textContent = "Chat" ;
chatLink . appendChild ( chatIcon ) ;
chatLink . appendChild ( chatText ) ;
const searchLink = nav . createEl ( "a" ) ;
searchLink . id = "search-nav" ;
searchLink . className = "khoj-nav search-nav" ;
const searchIcon = searchLink . createEl ( "span" ) ;
searchIcon . className = "khoj-nav-icon khoj-nav-icon-search" ;
const searchText = searchLink . createEl ( "span" ) ;
searchText . className = "khoj-nav-item-text" ;
searchText . textContent = "Search" ;
searchLink . appendChild ( searchIcon ) ;
searchLink . appendChild ( searchText ) ;
const similarLink = nav . createEl ( "a" ) ;
similarLink . id = "similar-nav" ;
similarLink . className = "khoj-nav similar-nav" ;
const similarIcon = searchLink . createEl ( "span" ) ;
similarIcon . id = "similar-nav-icon" ;
similarIcon . className = "khoj-nav-icon khoj-nav-icon-similar" ;
( 0 , import _obsidian . setIcon ) ( similarIcon , "webhook" ) ;
const similarText = searchLink . createEl ( "span" ) ;
similarText . className = "khoj-nav-item-text" ;
similarText . textContent = "Similar" ;
similarLink . appendChild ( similarIcon ) ;
similarLink . appendChild ( similarText ) ;
nav . appendChild ( chatLink ) ;
nav . appendChild ( searchLink ) ;
nav . appendChild ( similarLink ) ;
headerEl . appendChild ( titleEl ) ;
headerEl . appendChild ( nav ) ;
}
function copyParentText ( event , message , originalButton ) {
var _a ;
const button = event . currentTarget ;
if ( ! button || ! ( ( _a = button == null ? void 0 : button . parentNode ) == null ? void 0 : _a . textContent ) )
return ;
if ( ! ! button . firstChild )
button . removeChild ( button . firstChild ) ;
const textContent = message != null ? message : button . parentNode . textContent . trim ( ) ;
navigator . clipboard . writeText ( textContent ) . then ( ( ) => {
( 0 , import _obsidian . setIcon ) ( button , "copy-check" ) ;
setTimeout ( ( ) => {
( 0 , import _obsidian . setIcon ) ( button , originalButton ) ;
} , 1e3 ) ;
} ) . catch ( ( error ) => {
console . error ( "Error copying text to clipboard:" , error ) ;
const originalButtonText = button . innerHTML ;
button . innerHTML = "\u26D4\uFE0F" ;
setTimeout ( ( ) => {
button . innerHTML = originalButtonText ;
( 0 , import _obsidian . setIcon ) ( button , originalButton ) ;
} , 2e3 ) ;
} ) ;
return textContent ;
}
function createCopyParentText ( message , originalButton = "copy-plus" ) {
return function ( event ) {
return copyParentText ( event , message , originalButton ) ;
} ;
}
2024-07-13 19:25:17 +03:00
function jumpToPreviousView ( ) {
var _a ;
const editor = ( _a = this . app . workspace . getActiveFileView ( ) ) == null ? void 0 : _a . editor ;
if ( ! editor )
return ;
editor . focus ( ) ;
}
2024-06-13 21:01:37 +03:00
function pasteTextAtCursor ( text ) {
var _a ;
const editor = ( _a = this . app . workspace . getActiveFileView ( ) ) == null ? void 0 : _a . editor ;
if ( ! editor || ! text )
return ;
const cursor = editor . getCursor ( ) ;
if ( editor == null ? void 0 : editor . getSelection ( ) ) {
editor . replaceSelection ( text ) ;
} else if ( cursor ) {
editor . replaceRange ( text , cursor ) ;
}
}
2024-07-29 07:51:09 +03:00
function getFileFromPath ( sourceFiles , chosenFile ) {
2024-06-13 21:01:37 +03:00
let fileMatch = sourceFiles . sort ( ( a , b ) => b . path . length - a . path . length ) . find ( ( file ) => chosenFile . replace ( /\\/g , "/" ) . endsWith ( file . path ) ) ;
2024-07-29 07:51:09 +03:00
return fileMatch ;
}
function getLinkToEntry ( sourceFiles , chosenFile , chosenEntry ) {
let fileMatch = getFileFromPath ( sourceFiles , chosenFile ) ;
2024-06-13 21:01:37 +03:00
if ( fileMatch ) {
let resultHeading = fileMatch . extension !== "pdf" ? chosenEntry . split ( "\n" , 1 ) [ 0 ] : "" ;
let linkToEntry = resultHeading . startsWith ( "#" ) ? ` ${ fileMatch . path } ${ resultHeading } ` : fileMatch . path ;
console . log ( ` Link: ${ linkToEntry } , File: ${ fileMatch . path } , Heading: ${ resultHeading } ` ) ;
return linkToEntry ;
}
}
// src/settings.ts
var DEFAULT _SETTINGS = {
resultsCount : 6 ,
khojUrl : "https://app.khoj.dev" ,
khojApiKey : "" ,
connectedToBackend : false ,
autoConfigure : true ,
lastSync : /* @__PURE__ */ new Map ( ) ,
userInfo : null
} ;
var KhojSettingTab = class extends import _obsidian2 . PluginSettingTab {
constructor ( app , plugin ) {
super ( app , plugin ) ;
this . plugin = plugin ;
}
display ( ) {
var _a ;
const { containerEl } = this ;
containerEl . empty ( ) ;
let backendStatusEl = containerEl . createEl ( "small" , {
text : getBackendStatusMessage ( this . plugin . settings . connectedToBackend , ( _a = this . plugin . settings . userInfo ) == null ? void 0 : _a . email , this . plugin . settings . khojUrl , this . plugin . settings . khojApiKey )
} ) ;
let backendStatusMessage = "" ;
new import _obsidian2 . Setting ( containerEl ) . setName ( "Khoj URL" ) . setDesc ( "The URL of the Khoj backend." ) . addText ( ( text ) => text . setValue ( ` ${ this . plugin . settings . khojUrl } ` ) . onChange ( async ( value ) => {
this . plugin . settings . khojUrl = value . trim ( ) . replace ( /\/$/ , "" ) ;
( {
connectedToBackend : this . plugin . settings . connectedToBackend ,
userInfo : this . plugin . settings . userInfo ,
statusMessage : backendStatusMessage
} = await canConnectToBackend ( this . plugin . settings . khojUrl , this . plugin . settings . khojApiKey ) ) ;
await this . plugin . saveSettings ( ) ;
backendStatusEl . setText ( backendStatusMessage ) ;
} ) ) ;
new import _obsidian2 . Setting ( containerEl ) . setName ( "Khoj API Key" ) . setDesc ( "Use Khoj Cloud with your Khoj API Key" ) . addText ( ( text ) => text . setValue ( ` ${ this . plugin . settings . khojApiKey } ` ) . onChange ( async ( value ) => {
this . plugin . settings . khojApiKey = value . trim ( ) ;
( {
connectedToBackend : this . plugin . settings . connectedToBackend ,
userInfo : this . plugin . settings . userInfo ,
statusMessage : backendStatusMessage
} = await canConnectToBackend ( this . plugin . settings . khojUrl , this . plugin . settings . khojApiKey ) ) ;
await this . plugin . saveSettings ( ) ;
backendStatusEl . setText ( backendStatusMessage ) ;
} ) ) ;
new import _obsidian2 . Setting ( containerEl ) . setName ( "Results Count" ) . setDesc ( "The number of results to show in search and use for chat." ) . addSlider ( ( slider ) => slider . setLimits ( 1 , 10 , 1 ) . setValue ( this . plugin . settings . resultsCount ) . setDynamicTooltip ( ) . onChange ( async ( value ) => {
this . plugin . settings . resultsCount = value ;
await this . plugin . saveSettings ( ) ;
} ) ) ;
new import _obsidian2 . Setting ( containerEl ) . setName ( "Auto Sync" ) . setDesc ( "Automatically index your vault with Khoj." ) . addToggle ( ( toggle ) => toggle . setValue ( this . plugin . settings . autoConfigure ) . onChange ( async ( value ) => {
this . plugin . settings . autoConfigure = value ;
await this . plugin . saveSettings ( ) ;
} ) ) ;
let indexVaultSetting = new import _obsidian2 . Setting ( containerEl ) ;
indexVaultSetting . setName ( "Force Sync" ) . setDesc ( "Manually force Khoj to re-index your Obsidian Vault." ) . addButton ( ( button ) => button . setButtonText ( "Update" ) . setCta ( ) . onClick ( async ( ) => {
button . setButtonText ( "Updating \u{1F311}" ) ;
button . removeCta ( ) ;
indexVaultSetting = indexVaultSetting . setDisabled ( true ) ;
const progress _indicator = window . setInterval ( ( ) => {
if ( button . buttonEl . innerText === "Updating \u{1F311}" ) {
button . setButtonText ( "Updating \u{1F318}" ) ;
} else if ( button . buttonEl . innerText === "Updating \u{1F318}" ) {
button . setButtonText ( "Updating \u{1F317}" ) ;
} else if ( button . buttonEl . innerText === "Updating \u{1F317}" ) {
button . setButtonText ( "Updating \u{1F316}" ) ;
} else if ( button . buttonEl . innerText === "Updating \u{1F316}" ) {
button . setButtonText ( "Updating \u{1F315}" ) ;
} else if ( button . buttonEl . innerText === "Updating \u{1F315}" ) {
button . setButtonText ( "Updating \u{1F314}" ) ;
} else if ( button . buttonEl . innerText === "Updating \u{1F314}" ) {
button . setButtonText ( "Updating \u{1F313}" ) ;
} else if ( button . buttonEl . innerText === "Updating \u{1F313}" ) {
button . setButtonText ( "Updating \u{1F312}" ) ;
} else if ( button . buttonEl . innerText === "Updating \u{1F312}" ) {
button . setButtonText ( "Updating \u{1F311}" ) ;
}
} , 300 ) ;
this . plugin . registerInterval ( progress _indicator ) ;
this . plugin . settings . lastSync = await updateContentIndex ( this . app . vault , this . plugin . settings , this . plugin . settings . lastSync , true ) ;
new import _obsidian2 . Notice ( "\u2705 Updated Khoj index." ) ;
window . clearInterval ( progress _indicator ) ;
button . setButtonText ( "Update" ) ;
button . setCta ( ) ;
indexVaultSetting = indexVaultSetting . setDisabled ( false ) ;
} ) ) ;
}
} ;
// src/search_modal.ts
var import _obsidian3 = require ( "obsidian" ) ;
var KhojSearchModal = class extends import _obsidian3 . SuggestModal {
constructor ( app , setting , find _similar _notes = false ) {
super ( app ) ;
this . rerank = false ;
this . query = "" ;
this . app = app ;
this . setting = setting ;
this . find _similar _notes = find _similar _notes ;
this . inputEl . hidden = this . find _similar _notes ;
this . scope . register ( [ "Mod" ] , "Enter" , async ( ) => {
this . rerank = true ;
this . inputEl . dispatchEvent ( new Event ( "input" ) ) ;
this . rerank = false ;
} ) ;
this . scope . register ( [ "Shift" ] , "Enter" , async ( ) => {
if ( this . query != "" )
createNoteAndCloseModal ( this . query , this ) ;
} ) ;
this . scope . register ( [ "Ctrl" , "Shift" ] , "Enter" , async ( ) => {
if ( this . query != "" )
createNoteAndCloseModal ( this . query , this , { newLeaf : true } ) ;
} ) ;
const modalInstructions = [
{
command : "\u2191\u2193" ,
purpose : "to navigate"
} ,
{
command : "\u21B5" ,
purpose : "to open"
} ,
{
command : import _obsidian3 . Platform . isMacOS ? "cmd \u21B5" : "ctrl \u21B5" ,
purpose : "to rerank"
} ,
{
command : "esc" ,
purpose : "to dismiss"
}
] ;
this . setInstructions ( modalInstructions ) ;
this . setPlaceholder ( "Search with Khoj..." ) ;
}
async onOpen ( ) {
if ( this . find _similar _notes ) {
let file = this . app . workspace . getActiveFile ( ) ;
if ( file && file . extension === "md" ) {
this . rerank = true ;
this . inputEl . value = await this . app . vault . read ( file ) . then ( ( file _str ) => file _str . slice ( 0 , 42110 ) ) ;
this . inputEl . dispatchEvent ( new Event ( "input" ) ) ;
this . rerank = false ;
} else {
this . resultContainerEl . setText ( "Cannot find similar notes for non-markdown files" ) ;
}
}
}
async getSuggestions ( query ) {
let encodedQuery = encodeURIComponent ( query ) ;
let searchUrl = ` ${ this . setting . khojUrl } /api/search?q= ${ encodedQuery } &n= ${ this . setting . resultsCount } &r= ${ this . rerank } &client=obsidian ` ;
let headers = { "Authorization" : ` Bearer ${ this . setting . khojApiKey } ` } ;
let response = await ( 0 , import _obsidian3 . request ) ( { url : ` ${ searchUrl } ` , headers } ) ;
let results = JSON . parse ( response ) . filter ( ( result ) => {
var _a ;
return ! this . find _similar _notes || ! result . additional . file . endsWith ( ( _a = this . app . workspace . getActiveFile ( ) ) == null ? void 0 : _a . path ) ;
} ) . map ( ( result ) => {
return { entry : result . entry , file : result . additional . file } ;
} ) ;
this . query = query ;
return results ;
}
async renderSuggestion ( result , el ) {
2024-07-29 07:51:09 +03:00
var _a ;
2024-06-13 21:01:37 +03:00
let lines _to _render = 8 ;
let os _path _separator = result . file . includes ( "\\" ) ? "\\" : "/" ;
let filename = result . file . split ( os _path _separator ) . pop ( ) ;
el . createEl ( "div" , { cls : "khoj-result-file" } ) . setText ( filename != null ? filename : "" ) ;
let result _el = el . createEl ( "div" , { cls : "khoj-result-entry" } ) ;
2024-07-29 07:51:09 +03:00
let resultToRender = "" ;
let fileExtension = ( _a = filename == null ? void 0 : filename . split ( "." ) . pop ( ) ) != null ? _a : "" ;
if ( supportedImageFilesTypes . includes ( fileExtension ) && filename ) {
let linkToEntry = filename ;
let imageFiles = this . app . vault . getFiles ( ) . filter ( ( file ) => supportedImageFilesTypes . includes ( fileExtension ) ) ;
let fileInVault = getFileFromPath ( imageFiles , result . file ) ;
if ( fileInVault )
linkToEntry = this . app . vault . getResourcePath ( fileInVault ) ;
resultToRender = ` ![]( ${ linkToEntry } ) ` ;
} else {
result . entry = result . entry . replace ( /---[\n\r][\s\S]*---[\n\r]/ , "" ) ;
let entry _snipped _indicator = result . entry . split ( "\n" ) . length > lines _to _render ? " **...**" : "" ;
let snipped _entry = result . entry . split ( "\n" ) . slice ( 0 , lines _to _render ) . join ( "\n" ) ;
resultToRender = ` ${ snipped _entry } ${ entry _snipped _indicator } ` ;
}
import _obsidian3 . MarkdownRenderer . renderMarkdown ( resultToRender , result _el , result . file , null ) ;
2024-06-13 21:01:37 +03:00
}
async onChooseSuggestion ( result , _ ) {
const mdFiles = this . app . vault . getMarkdownFiles ( ) ;
2024-07-29 07:51:09 +03:00
const binaryFiles = this . app . vault . getFiles ( ) . filter ( ( file ) => supportedBinaryFileTypes . includes ( file . extension ) ) ;
let linkToEntry = getLinkToEntry ( mdFiles . concat ( binaryFiles ) , result . file , result . entry ) ;
2024-06-13 21:01:37 +03:00
if ( linkToEntry )
this . app . workspace . openLinkText ( linkToEntry , "" ) ;
}
} ;
// src/chat_view.ts
var import _obsidian5 = require ( "obsidian" ) ;
var DOMPurify = _ _toESM ( require _purify ( ) ) ;
// src/pane_view.ts
var import _obsidian4 = require ( "obsidian" ) ;
var KhojPaneView = class extends import _obsidian4 . ItemView {
constructor ( leaf , setting ) {
super ( leaf ) ;
this . setting = setting ;
}
async onOpen ( ) {
var _a , _b , _c , _d , _e ;
let { contentEl } = this ;
let headerEl = contentEl . createDiv ( { attr : { id : "khoj-header" , class : "khoj-header" } } ) ;
await populateHeaderPane ( headerEl , this . setting ) ;
( _a = headerEl . getElementsByClassName ( "chat-nav" ) [ 0 ] ) == null ? void 0 : _a . classList . add ( "khoj-nav-selected" ) ;
( _b = headerEl . getElementsByClassName ( "chat-nav" ) [ 0 ] ) == null ? void 0 : _b . addEventListener ( "click" , ( _ ) => {
this . activateView ( "khoj-chat-view" /* CHAT */ ) ;
} ) ;
( _c = headerEl . getElementsByClassName ( "search-nav" ) [ 0 ] ) == null ? void 0 : _c . addEventListener ( "click" , ( _ ) => {
new KhojSearchModal ( this . app , this . setting ) . open ( ) ;
} ) ;
( _d = headerEl . getElementsByClassName ( "similar-nav" ) [ 0 ] ) == null ? void 0 : _d . addEventListener ( "click" , ( _ ) => {
new KhojSearchModal ( this . app , this . setting , true ) . open ( ) ;
} ) ;
let similarNavSvgEl = ( _e = headerEl . getElementsByClassName ( "khoj-nav-icon-similar" ) [ 0 ] ) == null ? void 0 : _e . firstElementChild ;
if ( ! ! similarNavSvgEl )
similarNavSvgEl . id = "similar-nav-icon-svg" ;
}
async activateView ( viewType ) {
const { workspace } = this . app ;
let leaf = null ;
const leaves = workspace . getLeavesOfType ( viewType ) ;
if ( leaves . length > 0 ) {
leaf = leaves [ 0 ] ;
} else {
leaf = workspace . getRightLeaf ( false ) ;
await ( leaf == null ? void 0 : leaf . setViewState ( { type : viewType , active : true } ) ) ;
}
2024-07-13 19:25:17 +03:00
if ( leaf ) {
if ( viewType === "khoj-chat-view" /* CHAT */ ) {
let chatInput = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
if ( chatInput )
chatInput . focus ( ) ;
}
2024-06-13 21:01:37 +03:00
workspace . revealLeaf ( leaf ) ;
2024-07-13 19:25:17 +03:00
}
2024-06-13 21:01:37 +03:00
}
} ;
// src/chat_view.ts
var KhojChatView = class extends KhojPaneView {
constructor ( leaf , setting ) {
super ( leaf , setting ) ;
2024-07-13 19:25:17 +03:00
this . keyPressTimeout = null ;
this . scope = new import _obsidian5 . Scope ( this . app . scope ) ;
this . scope . register ( [ "Ctrl" ] , "n" , ( _ ) => this . createNewConversation ( ) ) ;
this . scope . register ( [ "Ctrl" ] , "o" , async ( _ ) => await this . toggleChatSessions ( ) ) ;
this . scope . register ( [ "Ctrl" ] , "f" , ( _ ) => new KhojSearchModal ( this . app , this . setting ) . open ( ) ) ;
this . scope . register ( [ "Ctrl" ] , "r" , ( _ ) => new KhojSearchModal ( this . app , this . setting , true ) . open ( ) ) ;
2024-06-13 21:01:37 +03:00
this . waitingForLocation = true ;
fetch ( "https://ipapi.co/json" ) . then ( ( response ) => response . json ( ) ) . then ( ( data ) => {
this . location = {
region : data . region ,
city : data . city ,
countryName : data . country _name ,
timezone : data . timezone
} ;
} ) . catch ( ( err ) => {
console . log ( err ) ;
} ) . finally ( ( ) => {
this . waitingForLocation = false ;
} ) ;
}
getViewType ( ) {
return "khoj-chat-view" /* CHAT */ ;
}
getDisplayText ( ) {
return "Khoj Chat" ;
}
getIcon ( ) {
return "message-circle" ;
}
2024-07-13 19:25:17 +03:00
async chat ( isVoice = false ) {
2024-06-13 21:01:37 +03:00
let input _el = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
let user _message = input _el . value . trim ( ) ;
input _el . value = "" ;
this . autoResize ( ) ;
2024-07-13 19:25:17 +03:00
await this . getChatResponse ( user _message , isVoice ) ;
2024-06-13 21:01:37 +03:00
}
async onOpen ( ) {
let { contentEl } = this ;
contentEl . addClass ( "khoj-chat" ) ;
super . onOpen ( ) ;
2024-06-24 09:58:08 +03:00
let defaultDomains = ` 'self' ${ this . setting . khojUrl } https://*.obsidian.md https://app.khoj.dev https://assets.khoj.dev ` ;
2024-06-13 21:01:37 +03:00
const defaultSrc = ` default-src ${ defaultDomains } ; ` ;
const scriptSrc = ` script-src ${ defaultDomains } 'unsafe-inline'; ` ;
2024-06-24 09:58:08 +03:00
const connectSrc = ` connect-src ${ this . setting . khojUrl } wss://*.obsidian.md/ https://ipapi.co/json; ` ;
2024-06-13 21:01:37 +03:00
const styleSrc = ` style-src ${ defaultDomains } 'unsafe-inline'; ` ;
2024-06-24 09:58:08 +03:00
const imgSrc = ` img-src * app: data:; ` ;
2024-06-13 21:01:37 +03:00
const childSrc = ` child-src 'none'; ` ;
const objectSrc = ` object-src 'none'; ` ;
const csp = ` ${ defaultSrc } ${ scriptSrc } ${ connectSrc } ${ styleSrc } ${ imgSrc } ${ childSrc } ${ objectSrc } ` ;
let chatBodyEl = contentEl . createDiv ( { attr : { id : "khoj-chat-body" , class : "khoj-chat-body" } } ) ;
let inputRow = contentEl . createDiv ( "khoj-input-row" ) ;
let chatSessions = inputRow . createEl ( "button" , {
text : "Chat Sessions" ,
attr : {
2024-07-13 19:25:17 +03:00
class : "khoj-input-row-button clickable-icon" ,
title : "Show Conversations (^O)"
2024-06-13 21:01:37 +03:00
}
} ) ;
chatSessions . addEventListener ( "click" , async ( _ ) => {
2024-07-13 19:25:17 +03:00
await this . toggleChatSessions ( ) ;
2024-06-13 21:01:37 +03:00
} ) ;
( 0 , import _obsidian5 . setIcon ) ( chatSessions , "history" ) ;
let chatInput = inputRow . createEl ( "textarea" , {
attr : {
id : "khoj-chat-input" ,
autofocus : "autofocus" ,
class : "khoj-chat-input option"
}
} ) ;
chatInput . addEventListener ( "input" , ( _ ) => {
this . onChatInput ( ) ;
} ) ;
chatInput . addEventListener ( "keydown" , ( event ) => {
this . incrementalChat ( event ) ;
} ) ;
2024-07-13 19:25:17 +03:00
this . contentEl . addEventListener ( "keydown" , this . handleKeyDown . bind ( this ) ) ;
this . contentEl . addEventListener ( "keyup" , this . handleKeyUp . bind ( this ) ) ;
2024-06-13 21:01:37 +03:00
let transcribe = inputRow . createEl ( "button" , {
text : "Transcribe" ,
attr : {
id : "khoj-transcribe" ,
2024-07-13 19:25:17 +03:00
class : "khoj-transcribe khoj-input-row-button clickable-icon " ,
title : "Start Voice Chat (^S)"
2024-06-13 21:01:37 +03:00
}
} ) ;
2024-07-13 19:25:17 +03:00
transcribe . addEventListener ( "mousedown" , ( event ) => {
this . startSpeechToText ( event ) ;
} ) ;
transcribe . addEventListener ( "mouseup" , async ( event ) => {
await this . stopSpeechToText ( event ) ;
2024-06-13 21:01:37 +03:00
} ) ;
transcribe . addEventListener ( "touchstart" , async ( event ) => {
await this . speechToText ( event ) ;
} ) ;
transcribe . addEventListener ( "touchend" , async ( event ) => {
await this . speechToText ( event ) ;
} ) ;
transcribe . addEventListener ( "touchcancel" , async ( event ) => {
await this . speechToText ( event ) ;
} ) ;
( 0 , import _obsidian5 . setIcon ) ( transcribe , "mic" ) ;
let send = inputRow . createEl ( "button" , {
text : "Send" ,
attr : {
id : "khoj-chat-send" ,
class : "khoj-chat-send khoj-input-row-button clickable-icon"
}
} ) ;
( 0 , import _obsidian5 . setIcon ) ( send , "arrow-up-circle" ) ;
let sendImg = send . getElementsByClassName ( "lucide-arrow-up-circle" ) [ 0 ] ;
sendImg . addEventListener ( "click" , async ( _ ) => {
await this . chat ( ) ;
} ) ;
let getChatHistorySucessfully = await this . getChatHistory ( chatBodyEl ) ;
let placeholderText = getChatHistorySucessfully ? "Message" : "Configure Khoj to enable chat" ;
chatInput . placeholder = placeholderText ;
chatInput . disabled = ! getChatHistorySucessfully ;
requestAnimationFrame ( ( ) => {
requestAnimationFrame ( ( ) => {
this . scrollChatToBottom ( ) ;
const chatInput2 = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
chatInput2 == null ? void 0 : chatInput2 . focus ( ) ;
} ) ;
} ) ;
}
2024-07-13 19:25:17 +03:00
startSpeechToText ( event , timeout = 200 ) {
if ( ! this . keyPressTimeout ) {
this . keyPressTimeout = setTimeout ( async ( ) => {
if ( this . sendMessageTimeout ) {
clearTimeout ( this . sendMessageTimeout ) ;
const sendButton = this . contentEl . getElementsByClassName ( "khoj-chat-send" ) [ 0 ] ;
( 0 , import _obsidian5 . setIcon ) ( sendButton , "arrow-up-circle" ) ;
let sendImg = sendButton . getElementsByClassName ( "lucide-arrow-up-circle" ) [ 0 ] ;
sendImg . addEventListener ( "click" , async ( _ ) => {
await this . chat ( ) ;
} ) ;
const chatInput = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
chatInput . value = "" ;
}
await this . speechToText ( event ) ;
} , timeout ) ;
}
}
async stopSpeechToText ( event ) {
if ( this . mediaRecorder ) {
await this . speechToText ( event ) ;
}
if ( this . keyPressTimeout ) {
clearTimeout ( this . keyPressTimeout ) ;
this . keyPressTimeout = null ;
}
}
handleKeyDown ( event ) {
if ( event . key === "s" && event . getModifierState ( "Control" ) )
this . startSpeechToText ( event ) ;
}
async handleKeyUp ( event ) {
if ( event . key === "s" && event . getModifierState ( "Control" ) )
await this . stopSpeechToText ( event ) ;
}
2024-06-13 21:01:37 +03:00
processOnlineReferences ( referenceSection , onlineContext ) {
let numOnlineReferences = 0 ;
for ( let subquery in onlineContext ) {
let onlineReference = onlineContext [ subquery ] ;
if ( onlineReference . organic && onlineReference . organic . length > 0 ) {
numOnlineReferences += onlineReference . organic . length ;
for ( let key in onlineReference . organic ) {
let reference = onlineReference . organic [ key ] ;
let polishedReference = this . generateOnlineReference ( referenceSection , reference , key ) ;
referenceSection . appendChild ( polishedReference ) ;
}
}
if ( onlineReference . knowledgeGraph && onlineReference . knowledgeGraph . length > 0 ) {
numOnlineReferences += onlineReference . knowledgeGraph . length ;
for ( let key in onlineReference . knowledgeGraph ) {
let reference = onlineReference . knowledgeGraph [ key ] ;
let polishedReference = this . generateOnlineReference ( referenceSection , reference , key ) ;
referenceSection . appendChild ( polishedReference ) ;
}
}
if ( onlineReference . peopleAlsoAsk && onlineReference . peopleAlsoAsk . length > 0 ) {
numOnlineReferences += onlineReference . peopleAlsoAsk . length ;
for ( let key in onlineReference . peopleAlsoAsk ) {
let reference = onlineReference . peopleAlsoAsk [ key ] ;
let polishedReference = this . generateOnlineReference ( referenceSection , reference , key ) ;
referenceSection . appendChild ( polishedReference ) ;
}
}
if ( onlineReference . webpages && onlineReference . webpages . length > 0 ) {
numOnlineReferences += onlineReference . webpages . length ;
for ( let key in onlineReference . webpages ) {
let reference = onlineReference . webpages [ key ] ;
let polishedReference = this . generateOnlineReference ( referenceSection , reference , key ) ;
referenceSection . appendChild ( polishedReference ) ;
}
}
}
return numOnlineReferences ;
}
generateOnlineReference ( messageEl , reference , index ) {
let title = reference . title || reference . link ;
let link = reference . link ;
let snippet = reference . snippet ;
let question = reference . question ? ` <b>Question:</b> ${ reference . question } <br><br> ` : "" ;
let referenceButton = messageEl . createEl ( "button" ) ;
let linkElement = referenceButton . createEl ( "a" ) ;
linkElement . setAttribute ( "href" , link ) ;
linkElement . setAttribute ( "target" , "_blank" ) ;
linkElement . setAttribute ( "rel" , "noopener noreferrer" ) ;
linkElement . classList . add ( "reference-link" ) ;
linkElement . setAttribute ( "title" , title ) ;
linkElement . textContent = title ;
referenceButton . id = ` ref- ${ index } ` ;
referenceButton . classList . add ( "reference-button" ) ;
referenceButton . classList . add ( "collapsed" ) ;
referenceButton . tabIndex = 0 ;
referenceButton . addEventListener ( "click" , function ( ) {
if ( this . classList . contains ( "collapsed" ) ) {
this . classList . remove ( "collapsed" ) ;
this . classList . add ( "expanded" ) ;
this . innerHTML = linkElement . outerHTML + ` <br><br> ${ question + snippet } ` ;
} else {
this . classList . add ( "collapsed" ) ;
this . classList . remove ( "expanded" ) ;
this . innerHTML = linkElement . outerHTML ;
}
} ) ;
return referenceButton ;
}
generateReference ( messageEl , referenceJson , index ) {
let reference = referenceJson . hasOwnProperty ( "compiled" ) ? referenceJson . compiled : referenceJson ;
let referenceFile = referenceJson . hasOwnProperty ( "file" ) ? referenceJson . file : null ;
const mdFiles = this . app . vault . getMarkdownFiles ( ) ;
const pdfFiles = this . app . vault . getFiles ( ) . filter ( ( file ) => file . extension === "pdf" ) ;
reference = reference . split ( "\n" ) . slice ( 1 ) . join ( "\n" ) ;
let escaped _ref = reference . replace ( /"/g , """ ) ;
let referenceButton = messageEl . createEl ( "button" ) ;
if ( referenceFile ) {
const linkToEntry = getLinkToEntry ( mdFiles . concat ( pdfFiles ) , referenceFile , reference ) ;
const linkElement = referenceButton . createEl ( "span" ) ;
linkElement . setAttribute ( "title" , escaped _ref ) ;
linkElement . textContent = referenceFile ;
if ( linkElement && linkToEntry ) {
linkElement . classList . add ( "reference-link" ) ;
linkElement . addEventListener ( "click" , ( event ) => {
event . stopPropagation ( ) ;
this . app . workspace . openLinkText ( linkToEntry , "" ) ;
} ) ;
}
}
let referenceText = referenceButton . createDiv ( ) ;
referenceText . textContent = escaped _ref ;
referenceButton . id = ` ref- ${ index } ` ;
referenceButton . classList . add ( "reference-button" ) ;
referenceButton . classList . add ( "collapsed" ) ;
referenceButton . tabIndex = 0 ;
referenceButton . addEventListener ( "click" , function ( ) {
if ( this . classList . contains ( "collapsed" ) ) {
this . classList . remove ( "collapsed" ) ;
this . classList . add ( "expanded" ) ;
} else {
this . classList . add ( "collapsed" ) ;
this . classList . remove ( "expanded" ) ;
}
} ) ;
return referenceButton ;
}
2024-07-13 19:25:17 +03:00
textToSpeech ( message , event = null ) {
let loader = document . createElement ( "span" ) ;
loader . classList . add ( "loader" ) ;
let speechButton ;
let speechIcon ;
if ( event === null ) {
let speechButtons = document . getElementsByClassName ( "speech-button" ) ;
speechButton = speechButtons [ speechButtons . length - 1 ] ;
let speechIcons = document . getElementsByClassName ( "speech-icon" ) ;
speechIcon = speechIcons [ speechIcons . length - 1 ] ;
} else {
speechButton = event . currentTarget ;
speechIcon = event . target ;
}
speechButton . appendChild ( loader ) ;
speechButton . disabled = true ;
const context = new AudioContext ( ) ;
let textToSpeechApi = ` ${ this . setting . khojUrl } /api/chat/speech?text= ${ encodeURIComponent ( message ) } ` ;
fetch ( textToSpeechApi , {
method : "POST" ,
headers : {
"Content-Type" : "application/json" ,
"Authorization" : ` Bearer ${ this . setting . khojApiKey } `
}
} ) . then ( ( response ) => response . arrayBuffer ( ) ) . then ( ( arrayBuffer ) => context . decodeAudioData ( arrayBuffer ) ) . then ( ( audioBuffer ) => {
const source = context . createBufferSource ( ) ;
source . buffer = audioBuffer ;
source . connect ( context . destination ) ;
source . start ( 0 ) ;
source . onended = function ( ) {
speechButton . removeChild ( loader ) ;
speechButton . disabled = false ;
} ;
} ) . catch ( ( err ) => {
console . error ( "Error playing speech:" , err ) ;
speechButton . removeChild ( loader ) ;
speechButton . disabled = false ;
} ) ;
}
2024-06-13 21:01:37 +03:00
formatHTMLMessage ( message , raw = false , willReplace = true ) {
2024-06-24 09:58:08 +03:00
message = message . replace ( /<s>\[INST\].+(<\/s>)?/g , "" ) ;
2024-06-13 21:01:37 +03:00
message = DOMPurify . sanitize ( message ) ;
2024-07-29 07:51:09 +03:00
let chatMessageBodyTextEl = this . contentEl . createDiv ( ) ;
chatMessageBodyTextEl . innerHTML = this . markdownTextToSanitizedHtml ( message , this ) ;
2024-06-13 21:01:37 +03:00
if ( willReplace === true ) {
2024-07-29 07:51:09 +03:00
this . renderActionButtons ( message , chatMessageBodyTextEl ) ;
2024-06-13 21:01:37 +03:00
}
2024-07-29 07:51:09 +03:00
return chatMessageBodyTextEl ;
2024-06-13 21:01:37 +03:00
}
2024-07-13 19:25:17 +03:00
markdownTextToSanitizedHtml ( markdownText , component ) {
2024-06-24 09:58:08 +03:00
let virtualChatMessageBodyTextEl = document . createElement ( "div" ) ;
2024-07-13 19:25:17 +03:00
import _obsidian5 . MarkdownRenderer . renderMarkdown ( markdownText , virtualChatMessageBodyTextEl , "" , component ) ;
2024-06-24 09:58:08 +03:00
virtualChatMessageBodyTextEl . innerHTML = virtualChatMessageBodyTextEl . innerHTML . replace ( / < i m g ( ? : ( ? ! s r c = [ " ' ] ( a p p : | d a t a : | h t t p s : \ / \ / g e n e r a t e d \ . k h o j \ . d e v ) ) . ) * ? > / g i s , " " ) ;
return DOMPurify . sanitize ( virtualChatMessageBodyTextEl . innerHTML ) ;
}
2024-06-13 21:01:37 +03:00
renderMessageWithReferences ( chatEl , message , sender , context , onlineContext , dt , intentType , inferredQueries ) {
if ( ! message )
return ;
let chatMessageEl ;
if ( intentType == null ? void 0 : intentType . includes ( "text-to-image" ) ) {
let imageMarkdown = this . generateImageMarkdown ( message , intentType , inferredQueries ) ;
chatMessageEl = this . renderMessage ( chatEl , imageMarkdown , sender , dt ) ;
} else {
chatMessageEl = this . renderMessage ( chatEl , message , sender , dt ) ;
}
if ( ( context == null || context . length == 0 ) && ( onlineContext == null || onlineContext && Object . keys ( onlineContext ) . length == 0 ) ) {
return ;
}
let references = { } ;
if ( ! ! context )
references [ "notes" ] = context ;
if ( ! ! onlineContext )
references [ "online" ] = onlineContext ;
let chatMessageBodyEl = chatMessageEl . getElementsByClassName ( "khoj-chat-message-text" ) [ 0 ] ;
chatMessageBodyEl . appendChild ( this . createReferenceSection ( references ) ) ;
}
generateImageMarkdown ( message , intentType , inferredQueries ) {
let imageMarkdown = "" ;
if ( intentType === "text-to-image" ) {
imageMarkdown = ` ![](data:image/png;base64, ${ message } ) ` ;
} else if ( intentType === "text-to-image2" ) {
imageMarkdown = ` ![]( ${ message } ) ` ;
} else if ( intentType === "text-to-image-v3" ) {
imageMarkdown = ` ![](data:image/webp;base64, ${ message } ) ` ;
}
if ( inferredQueries ) {
imageMarkdown += "\n\n**Inferred Query**:" ;
for ( let inferredQuery of inferredQueries ) {
imageMarkdown += `
$ { inferredQuery } ` ;
}
}
return imageMarkdown ;
}
renderMessage ( chatBodyEl , message , sender , dt , raw = false , willReplace = true ) {
let message _time = this . formatDate ( dt != null ? dt : new Date ( ) ) ;
let emojified _sender = sender == "khoj" ? "\u{1F3EE} Khoj" : "\u{1F914} You" ;
let chatMessageEl = chatBodyEl . createDiv ( {
attr : {
"data-meta" : ` ${ emojified _sender } at ${ message _time } ` ,
class : ` khoj-chat-message ${ sender } `
}
} ) ;
2024-07-29 07:51:09 +03:00
let chatMessageBodyEl = chatMessageEl . createDiv ( ) ;
chatMessageBodyEl . addClasses ( [ "khoj-chat-message-text" , sender ] ) ;
let chatMessageBodyTextEl = chatMessageBodyEl . createDiv ( ) ;
2024-06-13 21:01:37 +03:00
message = DOMPurify . sanitize ( message ) ;
if ( raw ) {
2024-07-29 07:51:09 +03:00
chatMessageBodyTextEl . innerHTML = message ;
2024-06-13 21:01:37 +03:00
} else {
2024-07-29 07:51:09 +03:00
chatMessageBodyTextEl . innerHTML = this . markdownTextToSanitizedHtml ( message , this ) ;
2024-06-13 21:01:37 +03:00
}
if ( willReplace === true ) {
2024-07-29 07:51:09 +03:00
this . renderActionButtons ( message , chatMessageBodyTextEl ) ;
2024-06-13 21:01:37 +03:00
}
chatMessageEl . style . userSelect = "text" ;
this . scrollChatToBottom ( ) ;
return chatMessageEl ;
}
createKhojResponseDiv ( dt ) {
2024-07-29 07:51:09 +03:00
let messageTime = this . formatDate ( dt != null ? dt : new Date ( ) ) ;
let chatBodyEl = this . contentEl . getElementsByClassName ( "khoj-chat-body" ) [ 0 ] ;
let chatMessageEl = chatBodyEl . createDiv ( {
2024-06-13 21:01:37 +03:00
attr : {
2024-07-29 07:51:09 +03:00
"data-meta" : ` \u {1F3EE} Khoj at ${ messageTime } ` ,
2024-06-13 21:01:37 +03:00
class : ` khoj-chat-message khoj `
}
2024-07-29 07:51:09 +03:00
} ) ;
2024-06-13 21:01:37 +03:00
this . scrollChatToBottom ( ) ;
2024-07-29 07:51:09 +03:00
return chatMessageEl ;
2024-06-13 21:01:37 +03:00
}
async renderIncrementalMessage ( htmlElement , additionalMessage ) {
2024-07-29 07:51:09 +03:00
this . chatMessageState . rawResponse += additionalMessage ;
2024-06-13 21:01:37 +03:00
htmlElement . innerHTML = "" ;
2024-07-29 07:51:09 +03:00
this . chatMessageState . rawResponse = DOMPurify . sanitize ( this . chatMessageState . rawResponse ) ;
htmlElement . innerHTML = this . markdownTextToSanitizedHtml ( this . chatMessageState . rawResponse , this ) ;
this . renderActionButtons ( this . chatMessageState . rawResponse , htmlElement ) ;
2024-06-13 21:01:37 +03:00
this . scrollChatToBottom ( ) ;
}
2024-07-29 07:51:09 +03:00
renderActionButtons ( message , chatMessageBodyTextEl ) {
2024-07-13 19:25:17 +03:00
var _a ;
2024-06-13 21:01:37 +03:00
let copyButton = this . contentEl . createEl ( "button" ) ;
2024-07-13 19:25:17 +03:00
copyButton . classList . add ( "chat-action-button" ) ;
2024-06-13 21:01:37 +03:00
copyButton . title = "Copy Message to Clipboard" ;
( 0 , import _obsidian5 . setIcon ) ( copyButton , "copy-plus" ) ;
copyButton . addEventListener ( "click" , createCopyParentText ( message ) ) ;
let pasteToFile = this . contentEl . createEl ( "button" ) ;
2024-07-13 19:25:17 +03:00
pasteToFile . classList . add ( "chat-action-button" ) ;
2024-06-13 21:01:37 +03:00
pasteToFile . title = "Paste Message to File" ;
( 0 , import _obsidian5 . setIcon ) ( pasteToFile , "clipboard-paste" ) ;
pasteToFile . addEventListener ( "click" , ( event ) => {
pasteTextAtCursor ( createCopyParentText ( message , "clipboard-paste" ) ( event ) ) ;
} ) ;
2024-07-13 19:25:17 +03:00
let speechButton = null ;
if ( ( _a = this . setting . userInfo ) == null ? void 0 : _a . is _active ) {
speechButton = this . contentEl . createEl ( "button" ) ;
speechButton . classList . add ( "chat-action-button" , "speech-button" ) ;
speechButton . title = "Listen to Message" ;
( 0 , import _obsidian5 . setIcon ) ( speechButton , "speech" ) ;
speechButton . addEventListener ( "click" , ( event ) => this . textToSpeech ( message , event ) ) ;
}
2024-07-29 07:51:09 +03:00
chatMessageBodyTextEl . append ( copyButton , pasteToFile ) ;
2024-07-13 19:25:17 +03:00
if ( speechButton ) {
2024-07-29 07:51:09 +03:00
chatMessageBodyTextEl . append ( speechButton ) ;
2024-07-13 19:25:17 +03:00
}
2024-06-13 21:01:37 +03:00
}
formatDate ( date ) {
let time _string = date . toLocaleTimeString ( "en-IN" , { hour : "2-digit" , minute : "2-digit" , hour12 : false } ) ;
let date _string = date . toLocaleString ( "en-IN" , { year : "numeric" , month : "short" , day : "2-digit" } ) . replace ( /-/g , " " ) ;
return ` ${ time _string } , ${ date _string } ` ;
}
2024-07-13 19:25:17 +03:00
createNewConversation ( ) {
let chatBodyEl = this . contentEl . getElementsByClassName ( "khoj-chat-body" ) [ 0 ] ;
2024-06-13 21:01:37 +03:00
chatBodyEl . innerHTML = "" ;
chatBodyEl . dataset . conversationId = "" ;
chatBodyEl . dataset . conversationTitle = "" ;
this . renderMessage ( chatBodyEl , "Hey \u{1F44B}\u{1F3FE}, what's up?" , "khoj" ) ;
}
2024-07-13 19:25:17 +03:00
async toggleChatSessions ( forceShow = false ) {
2024-06-13 21:01:37 +03:00
var _a ;
2024-07-13 19:25:17 +03:00
let chatBodyEl = this . contentEl . getElementsByClassName ( "khoj-chat-body" ) [ 0 ] ;
2024-06-13 21:01:37 +03:00
if ( ! forceShow && ( ( _a = this . contentEl . getElementsByClassName ( "side-panel" ) ) == null ? void 0 : _a . length ) > 0 ) {
chatBodyEl . innerHTML = "" ;
return this . getChatHistory ( chatBodyEl ) ;
}
chatBodyEl . innerHTML = "" ;
const sidePanelEl = this . contentEl . createDiv ( "side-panel" ) ;
const newConversationEl = sidePanelEl . createDiv ( "new-conversation" ) ;
const conversationHeaderTitleEl = newConversationEl . createDiv ( "conversation-header-title" ) ;
conversationHeaderTitleEl . textContent = "Conversations" ;
const newConversationButtonEl = newConversationEl . createEl ( "button" ) ;
newConversationButtonEl . classList . add ( "new-conversation-button" ) ;
newConversationButtonEl . classList . add ( "side-panel-button" ) ;
2024-07-13 19:25:17 +03:00
newConversationButtonEl . addEventListener ( "click" , ( _ ) => this . createNewConversation ( ) ) ;
2024-06-13 21:01:37 +03:00
( 0 , import _obsidian5 . setIcon ) ( newConversationButtonEl , "plus" ) ;
newConversationButtonEl . innerHTML += "New" ;
2024-07-13 19:25:17 +03:00
newConversationButtonEl . title = "New Conversation (^N)" ;
2024-06-13 21:01:37 +03:00
const existingConversationsEl = sidePanelEl . createDiv ( "existing-conversations" ) ;
const conversationListEl = existingConversationsEl . createDiv ( "conversation-list" ) ;
const conversationListBodyHeaderEl = conversationListEl . createDiv ( "conversation-list-header" ) ;
const conversationListBodyEl = conversationListEl . createDiv ( "conversation-list-body" ) ;
const chatSessionsUrl = ` ${ this . setting . khojUrl } /api/chat/sessions?client=obsidian ` ;
const headers = { "Authorization" : ` Bearer ${ this . setting . khojApiKey } ` } ;
try {
let response = await fetch ( chatSessionsUrl , { method : "GET" , headers } ) ;
let responseJson = await response . json ( ) ;
let conversationId = chatBodyEl . dataset . conversationId ;
if ( responseJson . length > 0 ) {
conversationListBodyHeaderEl . style . display = "block" ;
for ( let key in responseJson ) {
let conversation = responseJson [ key ] ;
let conversationSessionEl = this . contentEl . createEl ( "div" ) ;
let incomingConversationId = conversation [ "conversation_id" ] ;
conversationSessionEl . classList . add ( "conversation-session" ) ;
if ( incomingConversationId == conversationId ) {
conversationSessionEl . classList . add ( "selected-conversation" ) ;
}
const conversationTitle = conversation [ "slug" ] || ` New conversation \u {1F331} ` ;
const conversationSessionTitleEl = conversationSessionEl . createDiv ( "conversation-session-title" ) ;
conversationSessionTitleEl . textContent = conversationTitle ;
conversationSessionTitleEl . addEventListener ( "click" , ( ) => {
chatBodyEl . innerHTML = "" ;
chatBodyEl . dataset . conversationId = incomingConversationId ;
chatBodyEl . dataset . conversationTitle = conversationTitle ;
this . getChatHistory ( chatBodyEl ) ;
} ) ;
let conversationMenuEl = this . contentEl . createEl ( "div" ) ;
conversationMenuEl = this . addConversationMenu ( conversationMenuEl , conversationSessionEl , conversationTitle , conversationSessionTitleEl , chatBodyEl , incomingConversationId , incomingConversationId == conversationId ) ;
conversationSessionEl . appendChild ( conversationMenuEl ) ;
conversationListBodyEl . appendChild ( conversationSessionEl ) ;
chatBodyEl . appendChild ( sidePanelEl ) ;
}
}
} catch ( err ) {
return false ;
}
return true ;
}
addConversationMenu ( conversationMenuEl , conversationSessionEl , conversationTitle , conversationSessionTitleEl , chatBodyEl , incomingConversationId , selectedConversation ) {
conversationMenuEl . classList . add ( "conversation-menu" ) ;
const headers = { "Authorization" : ` Bearer ${ this . setting . khojApiKey } ` } ;
let editConversationTitleButtonEl = this . contentEl . createEl ( "button" ) ;
( 0 , import _obsidian5 . setIcon ) ( editConversationTitleButtonEl , "edit" ) ;
editConversationTitleButtonEl . title = "Rename" ;
2024-07-13 19:25:17 +03:00
editConversationTitleButtonEl . classList . add ( "edit-title-button" , "three-dot-menu-button-item" , "clickable-icon" ) ;
2024-06-13 21:01:37 +03:00
if ( selectedConversation )
editConversationTitleButtonEl . classList . add ( "selected-conversation" ) ;
editConversationTitleButtonEl . addEventListener ( "click" , ( event ) => {
event . stopPropagation ( ) ;
let conversationMenuChildren = conversationMenuEl . children ;
let totalItems = conversationMenuChildren . length ;
for ( let i = totalItems - 1 ; i >= 0 ; i -- ) {
conversationMenuChildren [ i ] . remove ( ) ;
}
let editConversationTitleInputEl = this . contentEl . createEl ( "input" ) ;
editConversationTitleInputEl . classList . add ( "conversation-title-input" ) ;
editConversationTitleInputEl . value = conversationTitle ;
editConversationTitleInputEl . addEventListener ( "click" , function ( event2 ) {
event2 . stopPropagation ( ) ;
} ) ;
editConversationTitleInputEl . addEventListener ( "keydown" , function ( event2 ) {
if ( event2 . key === "Enter" ) {
event2 . preventDefault ( ) ;
editConversationTitleSaveButtonEl . click ( ) ;
}
} ) ;
let editConversationTitleSaveButtonEl = this . contentEl . createEl ( "button" ) ;
conversationSessionTitleEl . replaceWith ( editConversationTitleInputEl ) ;
editConversationTitleSaveButtonEl . innerHTML = "Save" ;
2024-07-13 19:25:17 +03:00
editConversationTitleSaveButtonEl . classList . add ( "three-dot-menu-button-item" , "clickable-icon" ) ;
2024-06-13 21:01:37 +03:00
if ( selectedConversation )
editConversationTitleSaveButtonEl . classList . add ( "selected-conversation" ) ;
editConversationTitleSaveButtonEl . addEventListener ( "click" , ( event2 ) => {
event2 . stopPropagation ( ) ;
let newTitle = editConversationTitleInputEl . value ;
if ( newTitle != null ) {
let editURL = ` /api/chat/title?client=web&conversation_id= ${ incomingConversationId } &title= ${ newTitle } ` ;
fetch ( ` ${ this . setting . khojUrl } ${ editURL } ` , { method : "PATCH" , headers } ) . then ( ( response ) => response . ok ? response . json ( ) : Promise . reject ( response ) ) . then ( ( data ) => {
conversationSessionTitleEl2 . textContent = newTitle ;
} ) . catch ( ( err ) => {
return ;
} ) ;
const conversationSessionTitleEl2 = conversationSessionEl . createDiv ( "conversation-session-title" ) ;
conversationSessionTitleEl2 . textContent = newTitle ;
conversationSessionTitleEl2 . addEventListener ( "click" , ( ) => {
chatBodyEl . innerHTML = "" ;
chatBodyEl . dataset . conversationId = incomingConversationId ;
chatBodyEl . dataset . conversationTitle = conversationTitle ;
this . getChatHistory ( chatBodyEl ) ;
} ) ;
let newConversationMenuEl = this . contentEl . createEl ( "div" ) ;
newConversationMenuEl = this . addConversationMenu ( newConversationMenuEl , conversationSessionEl , newTitle , conversationSessionTitleEl2 , chatBodyEl , incomingConversationId , selectedConversation ) ;
conversationMenuEl . replaceWith ( newConversationMenuEl ) ;
editConversationTitleInputEl . replaceWith ( conversationSessionTitleEl2 ) ;
}
} ) ;
conversationMenuEl . appendChild ( editConversationTitleSaveButtonEl ) ;
} ) ;
conversationMenuEl . appendChild ( editConversationTitleButtonEl ) ;
let deleteConversationButtonEl = this . contentEl . createEl ( "button" ) ;
( 0 , import _obsidian5 . setIcon ) ( deleteConversationButtonEl , "trash" ) ;
deleteConversationButtonEl . title = "Delete" ;
2024-07-13 19:25:17 +03:00
deleteConversationButtonEl . classList . add ( "delete-conversation-button" , "three-dot-menu-button-item" , "clickable-icon" ) ;
2024-06-13 21:01:37 +03:00
if ( selectedConversation )
deleteConversationButtonEl . classList . add ( "selected-conversation" ) ;
deleteConversationButtonEl . addEventListener ( "click" , ( ) => {
let confirmation = confirm ( "Are you sure you want to delete this chat session?" ) ;
if ( ! confirmation )
return ;
let deleteURL = ` /api/chat/history?client=obsidian&conversation_id= ${ incomingConversationId } ` ;
fetch ( ` ${ this . setting . khojUrl } ${ deleteURL } ` , { method : "DELETE" , headers } ) . then ( ( response ) => response . ok ? response . json ( ) : Promise . reject ( response ) ) . then ( ( data ) => {
chatBodyEl . innerHTML = "" ;
chatBodyEl . dataset . conversationId = "" ;
chatBodyEl . dataset . conversationTitle = "" ;
2024-07-13 19:25:17 +03:00
this . toggleChatSessions ( true ) ;
2024-06-13 21:01:37 +03:00
} ) . catch ( ( err ) => {
return ;
} ) ;
} ) ;
conversationMenuEl . appendChild ( deleteConversationButtonEl ) ;
return conversationMenuEl ;
}
async getChatHistory ( chatBodyEl ) {
var _a , _b ;
let chatUrl = ` ${ this . setting . khojUrl } /api/chat/history?client=obsidian ` ;
if ( chatBodyEl . dataset . conversationId ) {
chatUrl += ` &conversation_id= ${ chatBodyEl . dataset . conversationId } ` ;
}
try {
let response = await fetch ( chatUrl , {
method : "GET" ,
headers : { "Authorization" : ` Bearer ${ this . setting . khojApiKey } ` }
} ) ;
let responseJson = await response . json ( ) ;
chatBodyEl . dataset . conversationId = responseJson . conversation _id ;
if ( responseJson . detail ) {
let setupMsg = "Hi \u{1F44B}\u{1F3FE}, to start chatting add available chat models options via [the Django Admin panel](/server/admin) on the Server" ;
this . renderMessage ( chatBodyEl , setupMsg , "khoj" , void 0 ) ;
return false ;
} else if ( responseJson . response ) {
chatBodyEl . dataset . conversationId = responseJson . response . conversation _id ;
chatBodyEl . dataset . conversationTitle = responseJson . response . slug || ` New conversation \u {1F331} ` ;
let chatLogs = ( ( _a = responseJson . response ) == null ? void 0 : _a . conversation _id ) ? ( _b = responseJson . response . chat ) != null ? _b : [ ] : responseJson . response ;
chatLogs . forEach ( ( chatLog ) => {
var _a2 , _b2 ;
this . renderMessageWithReferences ( chatBodyEl , chatLog . message , chatLog . by , chatLog . context , chatLog . onlineContext , new Date ( chatLog . created ) , ( _a2 = chatLog . intent ) == null ? void 0 : _a2 . type , ( _b2 = chatLog . intent ) == null ? void 0 : _b2 [ "inferred-queries" ] ) ;
} ) ;
}
} catch ( err ) {
let errorMsg = "Unable to get response from Khoj server \u2764\uFE0F\u200D\u{1FA79}. Ensure server is running or contact developers for help at [team@khoj.dev](mailto:team@khoj.dev) or in [Discord](https://discord.gg/BDgyabRM6e)" ;
this . renderMessage ( chatBodyEl , errorMsg , "khoj" , void 0 ) ;
return false ;
}
return true ;
}
2024-07-29 07:51:09 +03:00
convertMessageChunkToJson ( rawChunk ) {
if ( ( rawChunk == null ? void 0 : rawChunk . startsWith ( "{" ) ) && ( rawChunk == null ? void 0 : rawChunk . endsWith ( "}" ) ) ) {
try {
let jsonChunk = JSON . parse ( rawChunk ) ;
if ( ! jsonChunk . type )
jsonChunk = { type : "message" , data : jsonChunk } ;
return jsonChunk ;
} catch ( e ) {
return { type : "message" , data : rawChunk } ;
}
} else if ( rawChunk . length > 0 ) {
return { type : "message" , data : rawChunk } ;
}
return { type : "" , data : "" } ;
}
processMessageChunk ( rawChunk ) {
var _a , _b , _c ;
const chunk = this . convertMessageChunkToJson ( rawChunk ) ;
console . debug ( "Chunk:" , chunk ) ;
if ( ! chunk || ! chunk . type )
return ;
if ( chunk . type === "status" ) {
console . log ( ` status: ${ chunk . data } ` ) ;
const statusMessage = chunk . data ;
this . handleStreamResponse ( this . chatMessageState . newResponseTextEl , statusMessage , this . chatMessageState . loadingEllipsis , false ) ;
} else if ( chunk . type === "start_llm_response" ) {
console . log ( "Started streaming" , new Date ( ) ) ;
} else if ( chunk . type === "end_llm_response" ) {
console . log ( "Stopped streaming" , new Date ( ) ) ;
if ( this . chatMessageState . isVoice && ( ( _a = this . setting . userInfo ) == null ? void 0 : _a . is _active ) )
this . textToSpeech ( this . chatMessageState . rawResponse ) ;
this . finalizeChatBodyResponse ( this . chatMessageState . references , this . chatMessageState . newResponseTextEl ) ;
const liveQuery = this . chatMessageState . rawQuery ;
this . chatMessageState = {
newResponseTextEl : null ,
newResponseEl : null ,
loadingEllipsis : null ,
references : { } ,
rawResponse : "" ,
rawQuery : liveQuery ,
isVoice : false
} ;
} else if ( chunk . type === "references" ) {
this . chatMessageState . references = { "notes" : chunk . data . context , "online" : chunk . data . onlineContext } ;
} else if ( chunk . type === "message" ) {
const chunkData = chunk . data ;
if ( typeof chunkData === "object" && chunkData !== null ) {
this . handleJsonResponse ( chunkData ) ;
} else if ( typeof chunkData === "string" && ( ( _b = chunkData . trim ( ) ) == null ? void 0 : _b . startsWith ( "{" ) ) && ( ( _c = chunkData . trim ( ) ) == null ? void 0 : _c . endsWith ( "}" ) ) ) {
try {
const jsonData = JSON . parse ( chunkData . trim ( ) ) ;
this . handleJsonResponse ( jsonData ) ;
} catch ( e ) {
this . chatMessageState . rawResponse += chunkData ;
this . handleStreamResponse ( this . chatMessageState . newResponseTextEl , this . chatMessageState . rawResponse , this . chatMessageState . loadingEllipsis ) ;
}
} else {
this . chatMessageState . rawResponse += chunkData ;
this . handleStreamResponse ( this . chatMessageState . newResponseTextEl , this . chatMessageState . rawResponse , this . chatMessageState . loadingEllipsis ) ;
}
}
}
handleJsonResponse ( jsonData ) {
if ( jsonData . image || jsonData . detail ) {
this . chatMessageState . rawResponse = this . handleImageResponse ( jsonData , this . chatMessageState . rawResponse ) ;
} else if ( jsonData . response ) {
this . chatMessageState . rawResponse = jsonData . response ;
}
if ( this . chatMessageState . newResponseTextEl ) {
this . chatMessageState . newResponseTextEl . innerHTML = "" ;
this . chatMessageState . newResponseTextEl . appendChild ( this . formatHTMLMessage ( this . chatMessageState . rawResponse ) ) ;
}
}
async readChatStream ( response ) {
2024-06-13 21:01:37 +03:00
if ( response . body == null )
return ;
const reader = response . body . getReader ( ) ;
const decoder = new TextDecoder ( ) ;
2024-07-29 07:51:09 +03:00
const eventDelimiter = "\u2403\u{1F51A}\u2417" ;
let buffer = "" ;
2024-06-13 21:01:37 +03:00
while ( true ) {
const { value , done } = await reader . read ( ) ;
2024-07-13 19:25:17 +03:00
if ( done ) {
2024-07-29 07:51:09 +03:00
this . processMessageChunk ( buffer ) ;
buffer = "" ;
2024-06-13 21:01:37 +03:00
break ;
2024-07-13 19:25:17 +03:00
}
2024-07-29 07:51:09 +03:00
const chunk = decoder . decode ( value , { stream : true } ) ;
console . debug ( "Raw Chunk:" , chunk ) ;
buffer += chunk ;
let newEventIndex ;
while ( ( newEventIndex = buffer . indexOf ( eventDelimiter ) ) !== - 1 ) {
const event = buffer . slice ( 0 , newEventIndex ) ;
buffer = buffer . slice ( newEventIndex + eventDelimiter . length ) ;
if ( event )
this . processMessageChunk ( event ) ;
2024-06-13 21:01:37 +03:00
}
}
}
2024-07-13 19:25:17 +03:00
async getChatResponse ( query , isVoice = false ) {
2024-06-13 21:01:37 +03:00
if ( ! query || query === "" )
return ;
let chatBodyEl = this . contentEl . getElementsByClassName ( "khoj-chat-body" ) [ 0 ] ;
this . renderMessage ( chatBodyEl , query , "you" ) ;
2024-07-29 07:51:09 +03:00
let conversationId = chatBodyEl . dataset . conversationId ;
if ( ! conversationId ) {
2024-06-13 21:01:37 +03:00
let chatUrl2 = ` ${ this . setting . khojUrl } /api/chat/sessions?client=obsidian ` ;
let response2 = await fetch ( chatUrl2 , {
method : "POST" ,
headers : { "Authorization" : ` Bearer ${ this . setting . khojApiKey } ` }
} ) ;
let data = await response2 . json ( ) ;
2024-07-29 07:51:09 +03:00
conversationId = data . conversation _id ;
chatBodyEl . dataset . conversationId = conversationId ;
2024-06-13 21:01:37 +03:00
}
let encodedQuery = encodeURIComponent ( query ) ;
2024-07-29 07:51:09 +03:00
let chatUrl = ` ${ this . setting . khojUrl } /api/chat?q= ${ encodedQuery } &conversation_id= ${ conversationId } &n= ${ this . setting . resultsCount } &stream=true&client=obsidian ` ;
if ( ! ! this . location )
chatUrl += ` ®ion= ${ this . location . region } &city= ${ this . location . city } &country= ${ this . location . countryName } &timezone= ${ this . location . timezone } ` ;
let newResponseEl = this . createKhojResponseDiv ( ) ;
let newResponseTextEl = newResponseEl . createDiv ( ) ;
newResponseTextEl . classList . add ( "khoj-chat-message-text" , "khoj" ) ;
2024-06-13 21:01:37 +03:00
let loadingEllipsis = this . createLoadingEllipse ( ) ;
2024-07-29 07:51:09 +03:00
newResponseTextEl . appendChild ( loadingEllipsis ) ;
this . chatMessageState = {
newResponseEl ,
newResponseTextEl ,
loadingEllipsis ,
references : { } ,
rawQuery : query ,
rawResponse : "" ,
isVoice
} ;
2024-06-13 21:01:37 +03:00
let response = await fetch ( chatUrl , {
method : "GET" ,
headers : {
2024-07-29 07:51:09 +03:00
"Content-Type" : "text/plain" ,
2024-06-13 21:01:37 +03:00
"Authorization" : ` Bearer ${ this . setting . khojApiKey } `
}
} ) ;
try {
2024-07-29 07:51:09 +03:00
if ( response . body === null )
2024-06-13 21:01:37 +03:00
throw new Error ( "Response body is null" ) ;
2024-07-29 07:51:09 +03:00
await this . readChatStream ( response ) ;
2024-06-13 21:01:37 +03:00
} catch ( err ) {
2024-07-29 07:51:09 +03:00
console . error ( ` Khoj chat response failed with
2024-06-13 21:01:37 +03:00
$ { err } ` );
let errorMsg = "Sorry, unable to get response from Khoj backend \u2764\uFE0F\u200D\u{1FA79}. Retry or contact developers for help at <a href=mailto:'team@khoj.dev'>team@khoj.dev</a> or <a href='https://discord.gg/BDgyabRM6e'>on Discord</a>" ;
2024-07-29 07:51:09 +03:00
newResponseTextEl . textContent = errorMsg ;
2024-06-13 21:01:37 +03:00
}
}
flashStatusInChatInput ( message ) {
let chatInput = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
let originalPlaceholder = chatInput . placeholder ;
chatInput . placeholder = message ;
setTimeout ( ( ) => {
chatInput . placeholder = originalPlaceholder ;
} , 2e3 ) ;
}
async clearConversationHistory ( ) {
let chatBody = this . contentEl . getElementsByClassName ( "khoj-chat-body" ) [ 0 ] ;
let response = await ( 0 , import _obsidian5 . request ) ( {
url : ` ${ this . setting . khojUrl } /api/chat/history?client=obsidian ` ,
method : "DELETE" ,
headers : { "Authorization" : ` Bearer ${ this . setting . khojApiKey } ` }
} ) ;
try {
let result = JSON . parse ( response ) ;
if ( result . status !== "ok" ) {
throw new Error ( "Failed to clear conversation history" ) ;
} else {
let getChatHistoryStatus = await this . getChatHistory ( chatBody ) ;
if ( getChatHistoryStatus )
chatBody . innerHTML = "" ;
let statusMsg = getChatHistoryStatus ? result . message : "Failed to clear conversation history" ;
this . flashStatusInChatInput ( statusMsg ) ;
}
} catch ( err ) {
this . flashStatusInChatInput ( "Failed to clear conversation history" ) ;
}
}
async speechToText ( event ) {
2024-07-13 19:25:17 +03:00
var _a , _b ;
2024-06-13 21:01:37 +03:00
event . preventDefault ( ) ;
const transcribeButton = this . contentEl . getElementsByClassName ( "khoj-transcribe" ) [ 0 ] ;
const chatInput = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
const sendButton = this . contentEl . getElementsByClassName ( "khoj-chat-send" ) [ 0 ] ;
const generateRequestBody = async ( audioBlob , boundary _string ) => {
const boundary = ` ------ ${ boundary _string } ` ;
const chunks = [ ] ;
chunks . push ( new TextEncoder ( ) . encode ( ` ${ boundary } \r
` ));
chunks . push ( new TextEncoder ( ) . encode ( ` Content-Disposition: form-data; name="file"; filename="blob" \r
Content - Type : "application/octet-stream" \ r
\ r
` ));
chunks . push ( await audioBlob . arrayBuffer ( ) ) ;
chunks . push ( new TextEncoder ( ) . encode ( "\r\n" ) ) ;
await Promise . all ( chunks ) ;
chunks . push ( new TextEncoder ( ) . encode ( ` ${ boundary } -- \r
` ));
return await new Blob ( chunks ) . arrayBuffer ( ) ;
} ;
const sendToServer = async ( audioBlob ) => {
const boundary _string = ` Boundary ${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } ` ;
const requestBody = await generateRequestBody ( audioBlob , boundary _string ) ;
const response = await ( 0 , import _obsidian5 . requestUrl ) ( {
url : ` ${ this . setting . khojUrl } /api/transcribe?client=obsidian ` ,
method : "POST" ,
headers : { "Authorization" : ` Bearer ${ this . setting . khojApiKey } ` } ,
contentType : ` multipart/form-data; boundary=---- ${ boundary _string } ` ,
body : requestBody
} ) ;
2024-07-13 19:25:17 +03:00
let noSpeechText = [
"Thanks for watching!" ,
"Thanks for watching." ,
"Thank you for watching!" ,
"Thank you for watching." ,
"You" ,
"Bye."
] ;
let noSpeech = false ;
2024-06-13 21:01:37 +03:00
if ( response . status === 200 ) {
console . log ( response ) ;
2024-07-13 19:25:17 +03:00
noSpeech = noSpeechText . includes ( response . json . text . trimStart ( ) ) ;
if ( ! noSpeech )
chatInput . value += response . json . text . trimStart ( ) ;
2024-06-13 21:01:37 +03:00
this . autoResize ( ) ;
} else if ( response . status === 501 ) {
throw new Error ( "\u26D4\uFE0F Configure speech-to-text model on server." ) ;
} else if ( response . status === 422 ) {
throw new Error ( "\u26D4\uFE0F Audio file to large to process." ) ;
} else {
throw new Error ( "\u26D4\uFE0F Failed to transcribe audio." ) ;
}
2024-07-13 19:25:17 +03:00
if ( chatInput . value . length === 0 || noSpeech )
2024-06-13 21:01:37 +03:00
return ;
( 0 , import _obsidian5 . setIcon ) ( sendButton , "stop-circle" ) ;
let stopSendButtonImg = sendButton . getElementsByClassName ( "lucide-stop-circle" ) [ 0 ] ;
stopSendButtonImg . addEventListener ( "click" , ( _ ) => {
this . cancelSendMessage ( ) ;
} ) ;
stopSendButtonImg . getElementsByTagName ( "circle" ) [ 0 ] . style . animation = "countdown 3s linear 1 forwards" ;
2024-07-13 19:25:17 +03:00
stopSendButtonImg . getElementsByTagName ( "circle" ) [ 0 ] . style . color = "var(--icon-color-active)" ;
2024-06-13 21:01:37 +03:00
this . sendMessageTimeout = setTimeout ( ( ) => {
( 0 , import _obsidian5 . setIcon ) ( sendButton , "arrow-up-circle" ) ;
let sendImg = sendButton . getElementsByClassName ( "lucide-arrow-up-circle" ) [ 0 ] ;
sendImg . addEventListener ( "click" , async ( _ ) => {
await this . chat ( ) ;
} ) ;
2024-07-13 19:25:17 +03:00
this . chat ( true ) ;
2024-06-13 21:01:37 +03:00
} , 3e3 ) ;
} ;
const handleRecording = ( stream ) => {
const audioChunks = [ ] ;
const recordingConfig = { mimeType : "audio/webm" } ;
this . mediaRecorder = new MediaRecorder ( stream , recordingConfig ) ;
this . mediaRecorder . addEventListener ( "dataavailable" , function ( event2 ) {
if ( event2 . data . size > 0 )
audioChunks . push ( event2 . data ) ;
} ) ;
this . mediaRecorder . addEventListener ( "stop" , async function ( ) {
const audioBlob = new Blob ( audioChunks , { type : "audio/webm" } ) ;
await sendToServer ( audioBlob ) ;
} ) ;
this . mediaRecorder . start ( ) ;
2024-07-13 19:25:17 +03:00
transcribeButton . classList . add ( "loading-encircle" ) ;
2024-06-13 21:01:37 +03:00
} ;
2024-07-13 19:25:17 +03:00
if ( ! this . mediaRecorder || this . mediaRecorder . state === "inactive" || event . type === "touchstart" || event . type === "mousedown" || event . type === "keydown" ) {
2024-06-13 21:01:37 +03:00
( _a = navigator . mediaDevices . getUserMedia ( { audio : true } ) ) == null ? void 0 : _a . then ( handleRecording ) . catch ( ( e ) => {
this . flashStatusInChatInput ( "\u26D4\uFE0F Failed to access microphone" ) ;
} ) ;
2024-07-13 19:25:17 +03:00
} else if ( ( ( _b = this . mediaRecorder ) == null ? void 0 : _b . state ) === "recording" || event . type === "touchend" || event . type === "touchcancel" || event . type === "mouseup" || event . type === "keyup" ) {
2024-06-13 21:01:37 +03:00
this . mediaRecorder . stop ( ) ;
this . mediaRecorder . stream . getTracks ( ) . forEach ( ( track ) => track . stop ( ) ) ;
this . mediaRecorder = void 0 ;
2024-07-13 19:25:17 +03:00
transcribeButton . classList . remove ( "loading-encircle" ) ;
2024-06-13 21:01:37 +03:00
( 0 , import _obsidian5 . setIcon ) ( transcribeButton , "mic" ) ;
}
}
cancelSendMessage ( ) {
clearTimeout ( this . sendMessageTimeout ) ;
let sendButton = this . contentEl . getElementsByClassName ( "khoj-chat-send" ) [ 0 ] ;
( 0 , import _obsidian5 . setIcon ) ( sendButton , "arrow-up-circle" ) ;
let sendImg = sendButton . getElementsByClassName ( "lucide-arrow-up-circle" ) [ 0 ] ;
sendImg . addEventListener ( "click" , async ( _ ) => {
await this . chat ( ) ;
} ) ;
}
incrementalChat ( event ) {
if ( ! event . shiftKey && event . key === "Enter" ) {
event . preventDefault ( ) ;
this . chat ( ) ;
}
}
onChatInput ( ) {
const chatInput = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
chatInput . value = chatInput . value . trimStart ( ) ;
this . autoResize ( ) ;
}
autoResize ( ) {
const chatInput = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
const scrollTop = chatInput . scrollTop ;
chatInput . style . height = "0" ;
const scrollHeight = chatInput . scrollHeight + 8 ;
chatInput . style . height = Math . min ( scrollHeight , 200 ) + "px" ;
chatInput . scrollTop = scrollTop ;
this . scrollChatToBottom ( ) ;
}
scrollChatToBottom ( ) {
const chat _body _el = this . contentEl . getElementsByClassName ( "khoj-chat-body" ) [ 0 ] ;
if ( ! ! chat _body _el )
chat _body _el . scrollTop = chat _body _el . scrollHeight ;
}
createLoadingEllipse ( ) {
let loadingEllipsis = this . contentEl . createEl ( "div" ) ;
loadingEllipsis . classList . add ( "lds-ellipsis" ) ;
let firstEllipsis = this . contentEl . createEl ( "div" ) ;
firstEllipsis . classList . add ( "lds-ellipsis-item" ) ;
let secondEllipsis = this . contentEl . createEl ( "div" ) ;
secondEllipsis . classList . add ( "lds-ellipsis-item" ) ;
let thirdEllipsis = this . contentEl . createEl ( "div" ) ;
thirdEllipsis . classList . add ( "lds-ellipsis-item" ) ;
let fourthEllipsis = this . contentEl . createEl ( "div" ) ;
fourthEllipsis . classList . add ( "lds-ellipsis-item" ) ;
loadingEllipsis . appendChild ( firstEllipsis ) ;
loadingEllipsis . appendChild ( secondEllipsis ) ;
loadingEllipsis . appendChild ( thirdEllipsis ) ;
loadingEllipsis . appendChild ( fourthEllipsis ) ;
return loadingEllipsis ;
}
handleStreamResponse ( newResponseElement , rawResponse , loadingEllipsis , replace = true ) {
if ( ! newResponseElement )
return ;
2024-07-29 07:51:09 +03:00
if ( newResponseElement . getElementsByClassName ( "lds-ellipsis" ) . length > 0 && loadingEllipsis )
2024-06-13 21:01:37 +03:00
newResponseElement . removeChild ( loadingEllipsis ) ;
2024-07-29 07:51:09 +03:00
if ( replace )
2024-06-13 21:01:37 +03:00
newResponseElement . innerHTML = "" ;
newResponseElement . appendChild ( this . formatHTMLMessage ( rawResponse , false , replace ) ) ;
2024-07-29 07:51:09 +03:00
if ( ! replace && loadingEllipsis )
newResponseElement . appendChild ( loadingEllipsis ) ;
2024-06-13 21:01:37 +03:00
this . scrollChatToBottom ( ) ;
}
handleImageResponse ( imageJson , rawResponse ) {
var _a , _b ;
if ( imageJson . image ) {
const inferredQuery = ( _b = ( _a = imageJson . inferredQueries ) == null ? void 0 : _a [ 0 ] ) != null ? _b : "generated image" ;
if ( imageJson . intentType === "text-to-image" ) {
rawResponse += ` ![generated_image](data:image/png;base64, ${ imageJson . image } ) ` ;
} else if ( imageJson . intentType === "text-to-image2" ) {
rawResponse += ` ![generated_image]( ${ imageJson . image } ) ` ;
} else if ( imageJson . intentType === "text-to-image-v3" ) {
rawResponse = ` ![](data:image/webp;base64, ${ imageJson . image } ) ` ;
}
if ( inferredQuery ) {
rawResponse += `
* * Inferred Query * * :
$ { inferredQuery } ` ;
}
}
2024-07-29 07:51:09 +03:00
if ( imageJson . detail )
2024-06-13 21:01:37 +03:00
rawResponse += imageJson . detail ;
2024-07-29 07:51:09 +03:00
return rawResponse ;
2024-06-13 21:01:37 +03:00
}
finalizeChatBodyResponse ( references , newResponseElement ) {
if ( ! ! newResponseElement && references != null && Object . keys ( references ) . length > 0 ) {
newResponseElement . appendChild ( this . createReferenceSection ( references ) ) ;
}
this . scrollChatToBottom ( ) ;
let chatInput = this . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
if ( chatInput )
chatInput . removeAttribute ( "disabled" ) ;
}
createReferenceSection ( references ) {
let referenceSection = this . contentEl . createEl ( "div" ) ;
referenceSection . classList . add ( "reference-section" ) ;
referenceSection . classList . add ( "collapsed" ) ;
let numReferences = 0 ;
if ( references . hasOwnProperty ( "notes" ) ) {
numReferences += references [ "notes" ] . length ;
references [ "notes" ] . forEach ( ( reference , index ) => {
let polishedReference = this . generateReference ( referenceSection , reference , index ) ;
referenceSection . appendChild ( polishedReference ) ;
} ) ;
}
if ( references . hasOwnProperty ( "online" ) ) {
numReferences += this . processOnlineReferences ( referenceSection , references [ "online" ] ) ;
}
let referenceExpandButton = this . contentEl . createEl ( "button" ) ;
referenceExpandButton . classList . add ( "reference-expand-button" ) ;
referenceExpandButton . innerHTML = numReferences == 1 ? "1 reference" : ` ${ numReferences } references ` ;
referenceExpandButton . addEventListener ( "click" , function ( ) {
if ( referenceSection . classList . contains ( "collapsed" ) ) {
referenceSection . classList . remove ( "collapsed" ) ;
referenceSection . classList . add ( "expanded" ) ;
} else {
referenceSection . classList . add ( "collapsed" ) ;
referenceSection . classList . remove ( "expanded" ) ;
}
} ) ;
let referencesDiv = this . contentEl . createEl ( "div" ) ;
referencesDiv . classList . add ( "references" ) ;
referencesDiv . appendChild ( referenceExpandButton ) ;
referencesDiv . appendChild ( referenceSection ) ;
return referencesDiv ;
}
} ;
// src/main.ts
var Khoj = class extends import _obsidian6 . Plugin {
async onload ( ) {
await this . loadSettings ( ) ;
this . addCommand ( {
id : "search" ,
name : "Search" ,
callback : ( ) => {
new KhojSearchModal ( this . app , this . settings ) . open ( ) ;
}
} ) ;
this . addCommand ( {
id : "similar" ,
name : "Find similar notes" ,
editorCallback : ( ) => {
new KhojSearchModal ( this . app , this . settings , true ) . open ( ) ;
}
} ) ;
this . addCommand ( {
id : "chat" ,
name : "Chat" ,
callback : ( ) => {
this . activateView ( "khoj-chat-view" /* CHAT */ ) ;
}
} ) ;
this . registerView ( "khoj-chat-view" /* CHAT */ , ( leaf ) => new KhojChatView ( leaf , this . settings ) ) ;
this . addRibbonIcon ( "message-circle" , "Khoj" , ( _ ) => {
this . activateView ( "khoj-chat-view" /* CHAT */ ) ;
} ) ;
this . addSettingTab ( new KhojSettingTab ( this . app , this ) ) ;
this . indexingTimer = setInterval ( async ( ) => {
if ( this . settings . autoConfigure ) {
this . settings . lastSync = await updateContentIndex ( this . app . vault , this . settings , this . settings . lastSync ) ;
}
} , 60 * 60 * 1e3 ) ;
}
async loadSettings ( ) {
this . settings = Object . assign ( { } , DEFAULT _SETTINGS , await this . loadData ( ) ) ;
( { connectedToBackend : this . settings . connectedToBackend } = await canConnectToBackend ( this . settings . khojUrl , this . settings . khojApiKey , true ) ) ;
}
async saveSettings ( ) {
this . saveData ( this . settings ) ;
}
async onunload ( ) {
if ( this . indexingTimer )
clearInterval ( this . indexingTimer ) ;
this . unload ( ) ;
}
async activateView ( viewType ) {
2024-07-13 19:25:17 +03:00
var _a ;
2024-06-13 21:01:37 +03:00
const { workspace } = this . app ;
let leaf = null ;
const leaves = workspace . getLeavesOfType ( viewType ) ;
if ( leaves . length > 0 ) {
leaf = leaves [ 0 ] ;
} else {
leaf = workspace . getRightLeaf ( false ) ;
await ( leaf == null ? void 0 : leaf . setViewState ( { type : viewType , active : true } ) ) ;
}
2024-07-13 19:25:17 +03:00
if ( leaf ) {
const activeKhojLeaf = ( _a = workspace . getActiveViewOfType ( KhojPaneView ) ) == null ? void 0 : _a . leaf ;
if ( activeKhojLeaf === leaf )
jumpToPreviousView ( ) ;
else {
workspace . revealLeaf ( leaf ) ;
if ( viewType === "khoj-chat-view" /* CHAT */ ) {
let chatView = leaf . view ;
let chatInput = chatView . contentEl . getElementsByClassName ( "khoj-chat-input" ) [ 0 ] ;
if ( chatInput )
chatInput . focus ( ) ;
}
}
}
2024-06-13 21:01:37 +03:00
}
} ;
2024-07-13 19:25:17 +03:00
/*! @license DOMPurify 3.1.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.5/LICENSE */