[add] lsp manager
This commit is contained in:
parent
38a1909570
commit
c4f92f0402
39 changed files with 3481 additions and 25 deletions
|
|
@ -13,7 +13,7 @@ services:
|
|||
- ./webapp/custom:/srv/basex/lib/custom # jars
|
||||
- ./webapp/lsp:/srv/basex/webapp/lsp
|
||||
- ./webapp/static/clients:/srv/basex/webapp/static/clients
|
||||
|
||||
- ./webapp/lsp-manager:/srv/basex/webapp/lsp-manager
|
||||
# - ./repo:/srv/basex/repo
|
||||
environment:
|
||||
- "BASEX_JVM= -Dorg.basex.RESTXQERRORS=false -Dorg.basex.LOGEXCLUDE=/dba"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"cells":[{"kind":1,"language":"markdown","value":"XPath name charactors"},{"kind":2,"language":"xquery","value":"let $s:=`[A-Z]\r\n | '_'\r\n | [a-z]\r\n | [#xC0-#xD6]\r\n | [#xD8-#xF6]\r\n | [#xF8-#x2FF]\r\n | [#x370-#x37D]\r\n | [#x37F-#x1FFF]\r\n | [#x200C-#x200D]\r\n | [#x2070-#x218F]\r\n | [#x2C00-#x2FEF]\r\n | [#x3001-#xD7FF]\r\n | [#xF900-#xFDCF]\r\n | [#xFDF0-#xFFFD]\r\n `\r\nlet $sreg:= normalize-space($s)\r\n =>tokenize(\"\\|\")\r\n =!>normalize-space()\r\n =!>replace(\"(#x[0-9A-F]+)\",fn($s,$m){\r\n convert:integer-from-base(substring($m,3),16)\r\n =>codepoints-to-string()\r\n })\r\n =!>translate(\"'\",\"\")\r\n =>string-join(\"|\")\r\nreturn $sreg"},{"kind":1,"language":"markdown","value":"# Functions"},{"kind":2,"language":"xquery","value":"declare namespace fos=\"http://www.w3.org/xpath-functions/spec/namespace\";\r\ndoc(\"C:/Users/mrwhe/git/quodatum/basex-lsp/bundles/grammar/function-catalog.xml\")\r\n//fos:function[@prefix=\"fn\"]/@name"}]}
|
||||
{"cells":[{"kind":1,"language":"markdown","value":"XPath name charactors"},{"kind":2,"language":"xquery","value":"let $s:=`[A-Z]\r\n | '_'\r\n | [a-z]\r\n | [#xC0-#xD6]\r\n | [#xD8-#xF6]\r\n | [#xF8-#x2FF]\r\n | [#x370-#x37D]\r\n | [#x37F-#x1FFF]\r\n | [#x200C-#x200D]\r\n | [#x2070-#x218F]\r\n | [#x2C00-#x2FEF]\r\n | [#x3001-#xD7FF]\r\n | [#xF900-#xFDCF]\r\n | [#xFDF0-#xFFFD]\r\n `\r\nlet $sreg:= normalize-space($s)\r\n =>tokenize(\"\\|\")\r\n =!>normalize-space()\r\n =!>replace(\"(#x[0-9A-F]+)\",fn($s,$m){\r\n convert:integer-from-base(substring($m,3),16)\r\n =>codepoints-to-string()\r\n })\r\n =!>translate(\"'\",\"\")\r\n =>string-join(\"|\")\r\nreturn $sreg"},{"kind":1,"language":"markdown","value":"# Functions"},{"kind":2,"language":"xquery","value":"declare namespace fos=\"http://www.w3.org/xpath-functions/spec/namespace\";\r\ndoc(\"C:/Users/mrwhe/git/quodatum/basex-lsp/bundles/grammar/function-catalog.xml\")\r\n//fos:function[@prefix=\"fn\"]/@name"},{"kind":2,"language":"xquery","value":"let $a:=\"/srv/basex/webapp/lsp/etc/snippets.jsonc\"\r\n\r\nreturn unparsed-text($a)!replace(.,\"^(.*)//.*$\",\"$1\",\"m\")"}]}
|
||||
37
webapp/lsp-manager/api-jobs.xqm
Normal file
37
webapp/lsp-manager/api-jobs.xqm
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
module namespace joba = 'app/jobs';
|
||||
|
||||
declare variable $joba:names:=("id","type","state","user","duration","time","start");
|
||||
(:
|
||||
id: job ID
|
||||
type: type of the job (command, query, REST, RESTXQ, etc.)
|
||||
state: current state of the job: scheduled, queued, running, cached
|
||||
user: user who started the job
|
||||
duration: evaluation time (included if a job is running or if the result was cached)
|
||||
start: next start of job (included if a job will be executed repeatedly)
|
||||
time: time when job was registered
|
||||
:)
|
||||
declare function joba:jobs()
|
||||
as element(job)*
|
||||
{
|
||||
job:list()[. ne job:current()] ! job:list-details(.)
|
||||
};
|
||||
|
||||
declare function joba:table($jobs as element(job)*,$names as xs:string+)
|
||||
as element(table)
|
||||
{
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>{$names!<th>{ . }</th>}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{for $j in $jobs
|
||||
return <tr>
|
||||
{for $n in $names
|
||||
let $val:= $j/@*[name() eq $n]
|
||||
return <td>{$val/string()}</td>
|
||||
}
|
||||
</tr>}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
};
|
||||
80
webapp/lsp-manager/app.xqm
Normal file
80
webapp/lsp-manager/app.xqm
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
(:~
|
||||
app
|
||||
:)
|
||||
module namespace app = 'app/common';
|
||||
import module namespace cm = "app/cm" at "common.xqm";
|
||||
|
||||
|
||||
(:~
|
||||
: Redirects to the start page.
|
||||
: @return redirection
|
||||
:)
|
||||
declare %rest:path('/app')
|
||||
function app:redirect(
|
||||
) as element(rest:response) {
|
||||
web:redirect('/app/home')
|
||||
};
|
||||
|
||||
(:~
|
||||
: Returns a file.
|
||||
: @param $file file or unknown path
|
||||
: @return rest binary data
|
||||
:)
|
||||
declare %rest:path('/app/static/{$file=.+}')
|
||||
%output:method('basex')
|
||||
%perm:allow('public')
|
||||
function app:file( $file as xs:string)
|
||||
as item()+ {
|
||||
let $path:=file:base-dir() || 'static/' || $file
|
||||
return if(file:exists($path))
|
||||
then cm:response-file($path)
|
||||
else (
|
||||
<rest:response>
|
||||
<http:response status="404">
|
||||
<http:header name="Content-Language" value="en"/>
|
||||
<http:header name="Content-Type" value="text/plain; charset=utf-8"/>
|
||||
</http:response>
|
||||
</rest:response>,
|
||||
"The requested resource is not available."
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
|
||||
(:~ start page. :)
|
||||
declare %rest:path('/app/home')
|
||||
function app:home() as item()* {
|
||||
rest:init(true()),
|
||||
cm:htmx2("home.htm", map{"version" :"0.0.2"})
|
||||
};
|
||||
|
||||
|
||||
|
||||
declare %rest:path('/app/profile')
|
||||
function app:profile() {
|
||||
cm:htmx2("profile.htm", map{})
|
||||
};
|
||||
|
||||
declare %rest:path('/app/dev')
|
||||
function app:dev() {
|
||||
cm:htmx2("dev/home.htm", map{})
|
||||
};
|
||||
|
||||
|
||||
declare %rest:path('/app/notifications')
|
||||
function app:notifications() {
|
||||
cm:htmx2("notifications.htm", map{})
|
||||
};
|
||||
|
||||
declare %rest:path('/app/components')
|
||||
function app:components() {
|
||||
cm:htmx2("components.htm", map{})
|
||||
};
|
||||
|
||||
declare %rest:path('/app/tweets')
|
||||
function app:tweets() {
|
||||
cm:htmx2("tweets.htm", map{})
|
||||
};
|
||||
|
||||
|
||||
|
||||
88
webapp/lsp-manager/common.xqm
Normal file
88
webapp/lsp-manager/common.xqm
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
module namespace cm = 'app/cm';
|
||||
import module namespace ore = 'urn:quodatum:template:oregano' at "lib.xq/oregano.xqm";
|
||||
declare variable $cm:opts:=map{"base": file:resolve-path("views/", file:base-dir()),
|
||||
"layout": true(),
|
||||
"indent": "no"
|
||||
};
|
||||
declare variable $cm:model:=map{"_ctx": "app"};
|
||||
|
||||
declare function cm:header($path as xs:string?,$data as xs:base64Binary?,$content-type as xs:string?)
|
||||
as element(rest:response)
|
||||
{ switch(true())
|
||||
case exists($path) return
|
||||
web:response-header(
|
||||
map { 'media-type': web:content-type($path) },
|
||||
|
||||
map {
|
||||
'Cache-Control': 'max-age=3600,public',
|
||||
'Content-Length': file:size($path),
|
||||
'ETag': file:last-modified($path)
|
||||
}
|
||||
)
|
||||
|
||||
default return
|
||||
web:response-header(
|
||||
map { 'media-type':$content-type },
|
||||
(map:entry('Cache-Control', 'max-age=3600,public'),
|
||||
if(exists($data)) then map:entry('Content-Length', bin:length($data))
|
||||
)=>map:merge()
|
||||
)
|
||||
};
|
||||
|
||||
(:~ http response from file content:)
|
||||
declare function cm:response-file($path as xs:string)
|
||||
as item()*{
|
||||
if(file:exists($path))
|
||||
then (
|
||||
cm:header($path,(),()),
|
||||
file:read-binary($path)
|
||||
)else error(xs:QName('cm:not-found'),$path)
|
||||
()
|
||||
};
|
||||
|
||||
(:~ http response from data content:)
|
||||
declare function cm:response-data($data as xs:base64Binary,$content-type as xs:string)
|
||||
as item()*
|
||||
{
|
||||
cm:header((),$data,$content-type),
|
||||
$data
|
||||
|
||||
};
|
||||
|
||||
|
||||
(:~ http response for htmx :)
|
||||
declare function cm:htmx2($template as xs:string,$model as map(*))
|
||||
as item()*
|
||||
{
|
||||
cm:header((),(),"text/html"),
|
||||
ore:render($template,$cm:opts,$model)=>ore:serialize($cm:opts)
|
||||
};
|
||||
|
||||
|
||||
(:~ read a relative file :)
|
||||
declare function cm:read($path as xs:string)
|
||||
as xs:string{
|
||||
file:resolve-path( $path,file:base-dir())=>file:read-text()
|
||||
};
|
||||
|
||||
(:~ write a message to standard BaseX log passthru item :)
|
||||
declare function cm:trace($items as item()*,$msg as xs:string)
|
||||
as item()*{
|
||||
$items,admin:write-log($msg || ": " || serialize($items,map{"method":"basex"}),"MDUI")
|
||||
};
|
||||
|
||||
declare function cm:status-404()
|
||||
as item()*{
|
||||
<rest:response>
|
||||
<http:response status="404">
|
||||
<http:header name="Content-Language" value="en"/>
|
||||
<http:header name="Content-Type" value="text/plain; charset=utf-8"/>
|
||||
</http:response>
|
||||
</rest:response>,
|
||||
"The requested resource is not available."
|
||||
};
|
||||
|
||||
declare function cm:status($status as xs:integer,$msg as xs:string)
|
||||
as item()*{
|
||||
web:response-header((),(), map { 'status': $status, 'message': "OK"}),$msg
|
||||
};
|
||||
26
webapp/lsp-manager/error-reporting.xqm
Normal file
26
webapp/lsp-manager/error-reporting.xqm
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
module namespace _ = 'app/error-reporting';
|
||||
import module namespace cm = "app/cm" at "common.xqm";
|
||||
|
||||
|
||||
declare
|
||||
%rest:error("*")
|
||||
%rest:error-param("code", "{$code}")
|
||||
%rest:error-param("description", "{$description}")
|
||||
%rest:error-param("value", "{$value}")
|
||||
%rest:error-param("module", "{$module}")
|
||||
%rest:error-param("line-number", "{$line-number}")
|
||||
%rest:error-param("column-number","{$column-number}")
|
||||
%rest:error-param("additional", "{$additional}")
|
||||
function _:error($code,$description,$value,
|
||||
$module,$line-number,$column-number,$additional) {
|
||||
let $err:=map{"code":$code, "description":$description,
|
||||
"value": _:format($value), "module": $module,
|
||||
"line-number": $line-number, "column-number": $column-number,
|
||||
"additional": _:format($additional)}
|
||||
return cm:htmx2("error.htm", $err)
|
||||
};
|
||||
|
||||
declare function _:format($item)
|
||||
as xs:string{
|
||||
serialize($item,map{"method":"basex"})
|
||||
};
|
||||
1279
webapp/lsp-manager/lib.xq/Thyme.xqm
Normal file
1279
webapp/lsp-manager/lib.xq/Thyme.xqm
Normal file
File diff suppressed because it is too large
Load diff
235
webapp/lsp-manager/lib.xq/oregano.xqm
Normal file
235
webapp/lsp-manager/lib.xq/oregano.xqm
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
xquery version '4.0';
|
||||
(: oregano: A thymeleaf style templating library - INCOMPLETE
|
||||
Aims to be a compatable implementation of a subset https://www.thymeleaf.org/
|
||||
for use as a HTML5 templating engine with BaseX 10+
|
||||
supports: th:text, layout:decorate, layout:fragment
|
||||
@author Andy Bunce
|
||||
@status INCOMPLETE alpha
|
||||
@licence Apache 2
|
||||
:)
|
||||
module namespace ore = 'urn:quodatum:template:oregano';
|
||||
import module namespace p = 'Thyme' at "Thyme.xqm";
|
||||
|
||||
declare namespace th="http://www.thymeleaf.org";
|
||||
declare namespace layout="http://www.ultraq.net.nz/thymeleaf/layout";
|
||||
|
||||
declare variable $ore:default-options:=map{"base": file:base-dir(),
|
||||
"layout": true(),
|
||||
"indent": "no"
|
||||
};
|
||||
|
||||
(:~ updated html doc :)
|
||||
declare function ore:render($view as xs:string,$opts as map(*),$model as map(*))
|
||||
as document-node(){
|
||||
let $opts:=map:merge(($opts,$ore:default-options))
|
||||
let $doc:=(ore:doc($view,$opts)
|
||||
,message($model,"£ RENDER MODEL(" || $view ||"): "))
|
||||
let $a:= ore:update($doc,$opts,$model)
|
||||
return ore:decorate($a,$opts)
|
||||
};
|
||||
|
||||
(:~ update doc by evaluating th:each and th:text :)
|
||||
declare function ore:update($doc ,$opts as map(*),$model as map(*))
|
||||
{
|
||||
$doc update { (
|
||||
for $each in outermost(*//*[@th:each])
|
||||
let $p:=ore:parse-each($each/@th:each)
|
||||
return (
|
||||
replace node $each with (
|
||||
for $item in map:get($model,substring-after($p,","))
|
||||
let $model2:=map:put($model,substring-before($p,","),$item)
|
||||
(:=>trace("MODEL2:"):)
|
||||
let $x:=ore:update($each,$opts,$model2)
|
||||
return $x (:=>trace("DD: "):)
|
||||
))
|
||||
,delete node @th:each
|
||||
)
|
||||
}update{
|
||||
for $text in .//*[@th:text]
|
||||
where not($text/ancestor::*/@th:each)
|
||||
return (
|
||||
replace value of node $text with ore:expression($text/@th:text,$model)
|
||||
,delete node $text/@th:text
|
||||
)
|
||||
, ore:update-attrib(., "value", $model)=>prof:time("£ ore:update-attrib value: ")
|
||||
, ore:update-attrib(., "href", $model)=>prof:time("£ ore:update-attrib href: ")
|
||||
, ore:update-attrib(., "src", $model)=>prof:time("£ ore:update-attrib src: ")
|
||||
}
|
||||
};
|
||||
|
||||
(:~ update-attrib: use value of expression @th:name to update @name :)
|
||||
declare %updating function ore:update-attrib($context as node(),$name as xs:string,$model as map(*))
|
||||
as empty-sequence(){
|
||||
for $at in $context//*/@th:*[local-name(.) eq $name]
|
||||
where not($at/ancestor::*/@th:each)
|
||||
let $target:=$at/../@*[name() eq $name]=>trace("£ target: ")
|
||||
return (
|
||||
replace value of node $target with
|
||||
ore:expression($at, $model)=>trace("£ ore:expression: ")
|
||||
,delete node $at
|
||||
)
|
||||
};
|
||||
|
||||
(: load doc from base folder:)
|
||||
declare function ore:doc($file as xs:string,$opts as map(*))
|
||||
as document-node(){
|
||||
let $f:=file:resolve-path($file,$opts?base)=>trace("ore:doc")
|
||||
return doc($f)
|
||||
};
|
||||
|
||||
(:~ wrap with template named in root attribute @layout:decorate :)
|
||||
declare function ore:decorate($doc as document-node(),$opts as map(*))
|
||||
as document-node(){
|
||||
if(not($opts?layout and $doc/*/@layout:decorate))
|
||||
then $doc
|
||||
else
|
||||
let $wrap:=$doc/*/@layout:decorate=>replace(".+\{(.+)\}","$1")
|
||||
let $wdoc:=ore:doc($wrap,$opts)
|
||||
return $wdoc update{
|
||||
|
||||
replace node html/head/title
|
||||
with $doc/html/head/title,
|
||||
|
||||
insert node $doc/html/head/*[name() ne 'title']
|
||||
as last into html/head,
|
||||
|
||||
(: update matching fragments :)
|
||||
for $frag in //*/@layout:fragment
|
||||
where $doc//*/@layout:fragment=$frag
|
||||
return replace node //*[@layout:fragment=$frag]
|
||||
with ($doc//*[@layout:fragment=$frag] update {delete node @layout:fragment})
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
(:~ serialize as HTML5 :)
|
||||
declare function ore:serialize($doc as document-node(),$opts as map(*))
|
||||
as xs:string{
|
||||
serialize($doc ,
|
||||
map { 'method': 'html', 'version': '5.0'}
|
||||
)
|
||||
};
|
||||
|
||||
(:~ Return value of $atrib from model :)
|
||||
declare function ore:expression($atrb as xs:string,$model as map(*))
|
||||
as xs:string{
|
||||
let $p:=ore:parse-text($atrb)=>trace("ore:expression: ")
|
||||
let $exp:=substring-after($p,",")
|
||||
return switch(substring-before($p,","))
|
||||
|
||||
case "$" case "#" return
|
||||
(: @TODO assumes simple dotted :)
|
||||
ore:expression-value($exp,$model)
|
||||
|
||||
case "@" return
|
||||
let $x:=ore:expression-link($exp)=>ore:parse-params()
|
||||
return ore:template1($x,$model)
|
||||
|
||||
case "*" return
|
||||
error(xs:QName('ore:expression'),"*" || $exp)
|
||||
|
||||
default return
|
||||
error(xs:QName('ore:expression'),"bad exp start: " || $atrb)
|
||||
};
|
||||
|
||||
(:~ evaluate link $exp is body from "@{ body }" :)
|
||||
declare function ore:expression-link($exp as xs:string)
|
||||
as xs:string {
|
||||
switch (true())
|
||||
case starts-with($exp=>trace("EEEE: "), "http")
|
||||
case starts-with($exp, "//")
|
||||
return $exp
|
||||
case starts-with($exp, "/")
|
||||
return concat("/{_ctx}",$exp) (: prepend app:)
|
||||
case starts-with($exp, "~/")
|
||||
return substring($exp, 2)
|
||||
|
||||
default
|
||||
return $exp
|
||||
};
|
||||
|
||||
(:~ $exp is name or dotted name :)
|
||||
declare function ore:expression-value($exp as xs:string, $model as map (*))
|
||||
as xs:string {
|
||||
(: @TODO assumes simple dotted :)
|
||||
try {
|
||||
normalize-space($exp)=> tokenize("\.")=>fold-left( $model, map:get#2)=>string()
|
||||
} catch *{
|
||||
error(xs:QName('expression-value'),"bad: " || $exp)
|
||||
}
|
||||
};
|
||||
|
||||
(: extract param map if present in link
|
||||
@result 1st item string updated link , optional 2nd item is map of params
|
||||
:)
|
||||
declare function ore:parse-params($link as xs:string)
|
||||
as item()*
|
||||
{
|
||||
let $rex:="[^(]+\((.+)\)$"
|
||||
let $p:=replace($link,$rex,"$1")
|
||||
return if($p ne $link)
|
||||
then (: params present :)
|
||||
let $params:=(for $i in tokenize($p,",")
|
||||
let $v:=substring-after($i,"=")=>replace("['""](.*)['""]","$1") (: remove quotes :)
|
||||
return map:entry(substring-before($i,"="),$v)
|
||||
)=>map:merge()
|
||||
|
||||
let $ps:=map:keys($params)!concat(.,"=",$params(.))=>string-join("&")
|
||||
let $u:=substring-before($link,"(")
|
||||
let $anchor:=if(contains($u,"#"))
|
||||
then "#" || substring-after($u,"#")
|
||||
|
||||
return (concat(substring-before($u || "#","#"),"?",$ps,$anchor),$params)
|
||||
|
||||
else $link
|
||||
};
|
||||
|
||||
(:~ return "name,collection" from @th:each="name: ${collection}" or error :)
|
||||
declare function ore:parse-each($atrb as xs:string)
|
||||
as xs:string{
|
||||
let $rex:="^(\w+):\s*\$\{\s*(\w+)\s*\}\s*$"
|
||||
return if(matches($atrb,$rex))
|
||||
then replace($atrb,$rex,"$1,$2")
|
||||
else error(xs:QName('ore:parse-each'),"bad th:each: " || $atrb)
|
||||
};
|
||||
|
||||
(:~ return "type,expression" from @th:text or error
|
||||
Variable Expressions: ${...}
|
||||
Selection Variable Expressions: *{...}
|
||||
Message Expressions: #{...}
|
||||
Link URL Expressions: @{...}
|
||||
:)
|
||||
declare function ore:parse-text($atrb as xs:string)
|
||||
as xs:string{
|
||||
let $rex:="^([$*#@])\{\s*([^\s]+)\s*\}$"
|
||||
return if(matches($atrb,$rex))
|
||||
then replace($atrb,$rex,"$1,$2")
|
||||
else error(xs:QName('ore:parse-text'),"bad th:text: " || $atrb)
|
||||
};
|
||||
|
||||
|
||||
|
||||
(:~ REx parse :)
|
||||
declare function ore:parse($exp as xs:string)
|
||||
as element(*)
|
||||
{
|
||||
let $x:=p:parse-ThymeLeaf($exp)
|
||||
return if($x/self::ERROR)
|
||||
then error(xs:QName('ore:parse'),"bad: " || $exp)
|
||||
else $x
|
||||
};
|
||||
|
||||
|
||||
(:~ subst {key} strings in $tmp from model dotted values :)
|
||||
declare function ore:template1($tmp as xs:string, $model as map(*))
|
||||
as xs:string{
|
||||
(: @todo xquery 4 replace :)
|
||||
let $s:=analyze-string($tmp, "\{([^}]+)\}")
|
||||
update{
|
||||
for $m in fn:match
|
||||
|
||||
let $g:= normalize-space($m/fn:group)=> tokenize("\.")=>fold-left( $model, map:get#2)=>string()
|
||||
return replace value of node $m with $g
|
||||
}
|
||||
return string($s)
|
||||
};
|
||||
102
webapp/lsp-manager/lib.xq/outlet.xqm
Normal file
102
webapp/lsp-manager/lib.xq/outlet.xqm
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
xquery version '3.1';
|
||||
(:~ save conversion outputs to file
|
||||
ensures target folders created
|
||||
save multiple docs when root <wrapper> found
|
||||
optionally serialize XML with default namespace
|
||||
@author quodatum Andy Bunce
|
||||
:)
|
||||
module namespace outlet = 'urn:conversion:outlet';
|
||||
|
||||
(: serialization options
|
||||
key is arbitary name, value is BaseX serialization option map
|
||||
for xml serializations the non-standard 'ns' option supplies a namespace to use as default ' :)
|
||||
declare variable $outlet:serial:=map{
|
||||
"xvrl": map{"method":"xml","ns":"http://www.xproc.org/ns/xvrl"},
|
||||
"docbook":map{"method":"xml","ns":"http://docbook.org/ns/docbook"},
|
||||
"csv": map{"method":"text"},
|
||||
"xml": map{"method":"xml"}
|
||||
};
|
||||
|
||||
(:~save $doc to $dest unless empty,will create directories as required
|
||||
@param $opts serialization options and ns to set a default namespace :)
|
||||
declare function outlet:save($doc as item(),$dest as xs:string?,$opts as map(*)?)
|
||||
as xs:string?{
|
||||
if (exists($dest))
|
||||
then
|
||||
let $doc:=if($opts?ns)
|
||||
then outlet:change-element-ns-deep($doc,$opts?ns,"")
|
||||
else $doc
|
||||
return (
|
||||
file:create-dir(file:parent($dest)),
|
||||
file:write($dest,$doc,map:remove($opts,"ns")),
|
||||
$dest
|
||||
)
|
||||
};
|
||||
|
||||
(:~ save file or files if node is wrapper
|
||||
@return paths to saved files
|
||||
:)
|
||||
declare function outlet:save-wrapper($doc as item(),$dest as xs:string?,$opts as map(*)?)
|
||||
as xs:string*{
|
||||
if ($doc/wrapper)
|
||||
then $doc/wrapper/result! outlet:save(*,file:resolve-path(@href,$dest),$opts)
|
||||
else outlet:save($doc,$dest,$opts)
|
||||
};
|
||||
|
||||
(:~ from functx http://www.xqueryfunctions.com/xq/functx_change-element-ns-deep.html :)
|
||||
declare function outlet:change-element-ns-deep
|
||||
( $nodes as node()* ,
|
||||
$newns as xs:string ,
|
||||
$prefix as xs:string ) as node()* {
|
||||
|
||||
for $node in $nodes
|
||||
return if ($node instance of element())
|
||||
then (element
|
||||
{QName ($newns,
|
||||
concat($prefix,
|
||||
if ($prefix = '')
|
||||
then ''
|
||||
else ':',
|
||||
local-name($node)))}
|
||||
{$node/@*,
|
||||
outlet:change-element-ns-deep($node/node(),
|
||||
$newns, $prefix)})
|
||||
else if ($node instance of document-node())
|
||||
then outlet:change-element-ns-deep($node/node(),
|
||||
$newns, $prefix)
|
||||
else $node
|
||||
} ;
|
||||
|
||||
(:~ @see https://stackoverflow.com/a/8600987/3210344 :)
|
||||
declare function outlet:remove-prefixes($node as node(), $prefixes as xs:string*)
|
||||
as node(){
|
||||
typeswitch ($node)
|
||||
case element()
|
||||
return
|
||||
if ($prefixes = ('#all', prefix-from-QName(node-name($node)))) then
|
||||
element {QName(namespace-uri($node), local-name($node))} {
|
||||
$node/@*,
|
||||
$node/node()/outlet:remove-prefixes(., $prefixes)
|
||||
}
|
||||
else
|
||||
element {node-name($node)} {
|
||||
$node/@*,
|
||||
$node/node()/outlet:remove-prefixes(., $prefixes)
|
||||
}
|
||||
case document-node()
|
||||
return
|
||||
document {
|
||||
$node/node()/outlet:remove-prefixes(., $prefixes)
|
||||
}
|
||||
default
|
||||
return $node
|
||||
};
|
||||
|
||||
(:~ relative file paths below folder $path, matching $selector :)
|
||||
declare function outlet:select($path as xs:string,$selector as map(xs:string,item()*))
|
||||
as xs:string*
|
||||
{
|
||||
file:list($path,true(),$selector?pattern)
|
||||
[some $i in $selector?include satisfies contains(.,$i)]
|
||||
[every $x in $selector?exclude satisfies not(contains(.,$x))]
|
||||
};
|
||||
59
webapp/lsp-manager/lib.xq/tree.xqm
Normal file
59
webapp/lsp-manager/lib.xq/tree.xqm
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
xquery version "3.1";
|
||||
(:~
|
||||
: represant sequence of path strings as sequence of xml trees
|
||||
:)
|
||||
module namespace tree = 'quodatum.data.tree';
|
||||
|
||||
|
||||
(:~
|
||||
: convert path(s) to tree
|
||||
:)
|
||||
declare function tree:build($paths as xs:string*,$del as xs:string)
|
||||
{
|
||||
fn:fold-right($paths,
|
||||
(),
|
||||
function($this,$acc){ tree:merge($acc,tree:nest($this,$del)) }
|
||||
)
|
||||
};
|
||||
|
||||
(:~ convert a path to xml :)
|
||||
declare function tree:nest($path as xs:string,$del as xs:string)
|
||||
as element(*)
|
||||
{
|
||||
let $path:=if(starts-with($path,$del)) then $path else $del || $path
|
||||
let $parts:=fn:tokenize(($path),"\" || $del)
|
||||
return fn:fold-right(subsequence($parts,1,count($parts)-1),
|
||||
<file name="{$parts[last()]}" target="{$path}"/>,
|
||||
tree:wrap#2
|
||||
)
|
||||
};
|
||||
|
||||
declare function tree:wrap($this as xs:string,$acc)
|
||||
as element(*)
|
||||
{
|
||||
<directory name="{$this}">{$acc}</directory>
|
||||
};
|
||||
|
||||
|
||||
declare function tree:merge($a1 as element(*)?,$a2 as element(*)?)
|
||||
as element(*)*
|
||||
{
|
||||
if($a1/@name=$a2/@name) then
|
||||
let $n1:=$a1/*
|
||||
let $n2:=$a2/*
|
||||
|
||||
let $t:=(
|
||||
for $x in fn:distinct-values($n1/@name[.=$n2/@name]) (:both:)
|
||||
return tree:merge($a1/*[@name=$x],$a2/*[@name=$x]),
|
||||
|
||||
for $x in fn:distinct-values($n1/@name[fn:not(.=$n2/@name)]) (:only $a1 :)
|
||||
return $a1/*[@name=$x],
|
||||
|
||||
for $x in fn:distinct-values($n2/@name[fn:not(.=$n1/@name)]) (:only $a2 :)
|
||||
return $a2/*[@name=$x]
|
||||
)
|
||||
return tree:wrap($a1/@name,for $x in $t order by $x/@name return $x)
|
||||
else
|
||||
($a1,$a2)
|
||||
};
|
||||
|
||||
199
webapp/lsp-manager/lib.xq/wrangler.xqm
Normal file
199
webapp/lsp-manager/lib.xq/wrangler.xqm
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
xquery version '3.1';
|
||||
(:~
|
||||
Library to manage running multiple jobs using BaseX job:eval,
|
||||
where the jobs are the execution of one function for a set of arguments
|
||||
|
||||
The function will have signature `fn($key as xs:string) as element()`
|
||||
The function will typically create outputs and have side effects.
|
||||
|
||||
State information is persisted with the storage module, using the key '_wrangle'
|
||||
It is a map{$wid: {"jobs":}}
|
||||
requires basex 10+
|
||||
@licence BSD
|
||||
@author: quodatum
|
||||
@date: 2023/02/12
|
||||
:)
|
||||
module namespace wrangle = 'urn:quodatum:wrangler';
|
||||
(:~ semantic version :)
|
||||
declare variable $wrangle:version:="1.0.0";
|
||||
(:~ used in bindings to indicate a wrangle job and as store key :)
|
||||
declare variable $wrangle:id:="_wrangle";
|
||||
|
||||
(:~
|
||||
submit wrangle jobs for each $item
|
||||
@param wrangle data{xq:...,bindings:..}
|
||||
@return unique id for the job set
|
||||
:)
|
||||
declare function wrangle:queue($items as item()*,$wrangle as map(*))
|
||||
as xs:string{
|
||||
let $wid := random:uuid()
|
||||
let $jobs := $items!job:eval($wrangle?xq,
|
||||
map:merge(($wrangle?bindings(.),map:entry($wrangle:id,$wid))),
|
||||
map{"cache":true()}
|
||||
)
|
||||
|
||||
let $this := map:entry($wid,map:entry("jobs",$jobs!map:entry(.,
|
||||
map{
|
||||
"complete":false(),
|
||||
"details":job:list-details(.)
|
||||
})))
|
||||
|
||||
let $_:=store:put($wrangle:id,map:merge((($this,wrangle:store()))))
|
||||
return $wid
|
||||
};
|
||||
|
||||
(:~ active wrangle ids :)
|
||||
declare function wrangle:active()
|
||||
as xs:string*{
|
||||
job:list()!job:bindings(.)?($wrangle:id)=>distinct-values()
|
||||
};
|
||||
|
||||
(:~ known wrangles :)
|
||||
declare function wrangle:list()
|
||||
as xs:string*{
|
||||
wrangle:store()=>map:keys()
|
||||
};
|
||||
|
||||
(:~ details for $wid wrangle :)
|
||||
declare function wrangle:list-details($wid as xs:string)
|
||||
as map(*){
|
||||
wrangle:store()=>map:get($wid)
|
||||
};
|
||||
|
||||
(:~ all wrangled jobs :)
|
||||
declare function wrangle:job-list()
|
||||
as xs:string*{
|
||||
job:list()[job:bindings(.)=>map:contains($wrangle:id)]
|
||||
};
|
||||
|
||||
(:~ jobs for wrangle id :)
|
||||
declare function wrangle:job-list($wid as xs:string)
|
||||
as xs:string*{
|
||||
job:list()[job:bindings(.)?($wrangle:id) eq $wid]
|
||||
};
|
||||
|
||||
(:~ is wrangle id finished (or unknown) :)
|
||||
declare function wrangle:finished($wid as xs:string)
|
||||
as xs:string*{
|
||||
every $job in job:list()[job:bindings(.)?($wrangle:id) eq $wid] satisfies job:finished($job)
|
||||
};
|
||||
|
||||
(:~ wait wrangle id finished (or unknown) :)
|
||||
declare function wrangle:wait($wid as xs:string)
|
||||
as empty-sequence(){
|
||||
let $done:=every $job in job:list()[job:bindings(.)?($wrangle:id) eq $wid]
|
||||
satisfies empty(job:wait($job))
|
||||
return if($done) then ()
|
||||
};
|
||||
|
||||
(:~ cancel wrangle id :)
|
||||
declare function wrangle:remove($wid as xs:string)
|
||||
as empty-sequence(){
|
||||
job:list()[job:bindings(.)?($wrangle:id) eq $wid]!job:remove(.),
|
||||
store:put($wrangle:id,wrangle:store()=>map:remove($wid))
|
||||
};
|
||||
|
||||
(:~ tally of non-zero job status for $wid "scheduled", "queued", "running", "cached" :)
|
||||
declare function wrangle:status($wid as xs:string)
|
||||
as map(*){
|
||||
wrangle:job-list($wid)!job:list-details(.)/@state/string()
|
||||
=>fold-left(map{},wrangle:tally-count#2)
|
||||
};
|
||||
|
||||
(:~ job-results with no error as sequence:)
|
||||
declare function wrangle:results($wid as xs:string)
|
||||
as item()*{
|
||||
wrangle:job-list($wid)!wrangle:job-result(.)[not(?error)]?result
|
||||
};
|
||||
|
||||
(:~ error counts keyed on $err:code :)
|
||||
declare function wrangle:errors($wid as xs:string)
|
||||
as map(*){
|
||||
wrangle:job-list($wid)!wrangle:job-result(.)[?error]?result?code!string()
|
||||
=>fold-left(map{},wrangle:tally-count#2)
|
||||
};
|
||||
|
||||
(:~ key is $err:code values are joblists :)
|
||||
declare function wrangle:jobs-by-error($wid as xs:string)
|
||||
as map(*){
|
||||
(for $jobId in wrangle:job-list($wid)
|
||||
let $result:=wrangle:job-result($jobId)[?error]
|
||||
where exists($result)
|
||||
return map:entry($result?result?code!string(),$jobId)
|
||||
)
|
||||
=> map:merge( map{"duplicates":"combine"})
|
||||
};
|
||||
|
||||
(:~ return key for job:)
|
||||
declare function wrangle:job-key($jobId as xs:string)
|
||||
as xs:string{
|
||||
let $b:=job:bindings($jobId)
|
||||
return $b?(map:keys($b)[. ne $wrangle:id])
|
||||
};
|
||||
|
||||
(:~ return map from peek at result:)
|
||||
declare function wrangle:job-result($jobId as xs:string)
|
||||
as map(*){
|
||||
try{
|
||||
map{
|
||||
"error":false(),
|
||||
"result": job:result($jobId,map{"keep":true()})
|
||||
}
|
||||
}catch *{
|
||||
map{
|
||||
"error":true(),
|
||||
"result": map{"description": $err:description,
|
||||
"code": $err:code,
|
||||
"line": $err:column-number,
|
||||
"additional": $err:additional,
|
||||
"value":$err:value
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(:~ XQuery for background service :)
|
||||
declare function wrangle:service()
|
||||
as xs:string{
|
||||
``[
|
||||
import module namespace wrangle = 'urn:quodatum:wrangler:v1'; at "`{ static-base-uri() }`";
|
||||
let $done:=wrangle:job-list()[job:finished(.)]
|
||||
if(exists($done))
|
||||
then let $w:=store:get($wrangle:id)
|
||||
for $job in $done
|
||||
group by $wid= job:bindings($job)?($wrangle:id)
|
||||
for $job in $job
|
||||
let $_:=store:put($wrangle:id)
|
||||
]``
|
||||
|
||||
};
|
||||
(:~ schedule as service :)
|
||||
declare function wrangle:schedule-service()
|
||||
as xs:string{
|
||||
wrangle:service()
|
||||
=>job:eval((), map { 'id':$wrangle:id, 'service':true(),
|
||||
'interval': 'PT1S','log': $wrangle:id})
|
||||
};
|
||||
|
||||
(:~ cached data as map :)
|
||||
declare function wrangle:store()
|
||||
as map(*){
|
||||
store:get-or-put($wrangle:id,function(){map{}})
|
||||
};
|
||||
|
||||
(:~ @return map string->count for fold-left :)
|
||||
declare %private function wrangle:tally-count($r as map(*),$this as xs:string)
|
||||
as map(*){
|
||||
map:merge(
|
||||
(map:entry($this,if(map:contains($r,$this)) then $r($this)+1 else 1),$r),
|
||||
map{"duplicates":"use-first"}
|
||||
)
|
||||
};
|
||||
(:~ @return map string->(string*) for fold-left :)
|
||||
declare %private function wrangle:tally-list($r as map(*),$key as xs:string,$value as xs:string)
|
||||
as map(*){
|
||||
map:merge(
|
||||
(map:entry($key,$value),$r),
|
||||
map{"duplicates":"combine"}
|
||||
)
|
||||
};
|
||||
58
webapp/lsp-manager/views/components.htm
Normal file
58
webapp/lsp-manager/views/components.htm
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Components</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<h2>Components (test page) <a href="/app/static/index.html" target="wc">wc</a></h2>
|
||||
<p>You have no direct messages
|
||||
<sl-copy-button value="copy text is easy {Date().now}" copy-label="Click to copy"
|
||||
success-label="Text copied!" error-label="Whoops, your browser doesn't support this!"></sl-copy-button>
|
||||
</p>
|
||||
<sl-tree selection="multiple">
|
||||
<sl-tree-item lazy="lazy" onsl-lazy-load='{updateLazy}'>Available Trees</sl-tree-item>
|
||||
</sl-tree>
|
||||
<script type="module">
|
||||
const lazyItem = document.querySelector('sl-tree-item[lazy]');
|
||||
|
||||
lazyItem.addEventListener('sl-lazy-load', () => {
|
||||
// Simulate asynchronous loading
|
||||
setTimeout(() => {
|
||||
const subItems = ['Birch', 'Cedar', 'Maple', 'Pine'];
|
||||
|
||||
for (const item of subItems) {
|
||||
const treeItem = document.createElement('sl-tree-item');
|
||||
treeItem.innerText = item;
|
||||
lazyItem.append(treeItem);
|
||||
}
|
||||
|
||||
// Disable lazy mode once the content has been loaded
|
||||
lazyItem.lazy = false;
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
<hr />
|
||||
<sl-carousel navigation="navigation" pagination="pagination" slides-per-page="6" slides-per-move="6">
|
||||
<sl-carousel-item style="background: var(--sl-color-red-200);">Slide 1</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-orange-200);">Slide 2</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-yellow-200);">Slide 3</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-green-200);">Slide 4</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-blue-200);">Slide 5</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-violet-200);">Slide 6</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-red-200);">Slide 21</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-orange-200);">Slide 22</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-yellow-200);">Slide 23</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-green-200);">Slide 24</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-blue-200);">Slide 25</sl-carousel-item>
|
||||
<sl-carousel-item style="background: var(--sl-color-violet-200);">Slide 26</sl-carousel-item>
|
||||
</sl-carousel>
|
||||
|
||||
<hr />
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
21
webapp/lsp-manager/views/dev/dba-iframe.htm
Normal file
21
webapp/lsp-manager/views/dev/dba-iframe.htm
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>dev</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item href="/app/dev">DEV</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>dba</sl-breadcrumb-item>
|
||||
|
||||
</sl-breadcrumb>
|
||||
<iframe src="/dba/logs?input=%2Fapp%2F|£" allowScripts="true"
|
||||
style="width:100%;height:80vh;overflow:clip;"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
40
webapp/lsp-manager/views/dev/home.htm
Normal file
40
webapp/lsp-manager/views/dev/home.htm
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Dev home</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">Dev</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a href="/app/dev/dba" class="nav-link" >dba</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/app/dev/jobs" class="nav-link" >jobs</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/dba/logs?input=%2Fapp%2F|£" target="_blank" class="nav-link" >DBA <sl-icon name="box-arrow-up-right"></sl-icon></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/app/api" target="_blank" class="nav-link" >wadl <sl-icon name="box-arrow-up-right"></sl-icon></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
list tasks etc
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
28
webapp/lsp-manager/views/dev/job.htm
Normal file
28
webapp/lsp-manager/views/dev/job.htm
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Jobs</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class="container-fluid">
|
||||
<div class="d-flex ">
|
||||
<sl-breadcrumb class="flex-grow-1" >
|
||||
<sl-breadcrumb-item href="/app/jobs">Jobs home</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>jobs <span class="badge bg-secondary">4</span></sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
<sl-switch id="job-refresh" checked="checked" >Refresh</sl-switch>
|
||||
</div>
|
||||
|
||||
<h2>Jobs</h2>
|
||||
<hr />
|
||||
<div hx-get="/app/jobs/table" hx-trigger="every 1s [htmx.find('#job-refresh').checked]" >
|
||||
Nothing Yet!
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
27
webapp/lsp-manager/views/error.htm
Normal file
27
webapp/lsp-manager/views/error.htm
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Error</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class="container">
|
||||
<h2 >Error: <span th:text="${code}" class="btn btn-danger"></span></h2>
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd th:text="${description}"></dd>
|
||||
<dt>Value</dt>
|
||||
<dd th:text="${value}"></dd>
|
||||
<dt>Module</dt>
|
||||
<dd ><span th:text="${module}"/>[<span th:text="${line-number}"/>,<span th:text="${column-number}"/>]</dd>
|
||||
<dt>Additional</dt>
|
||||
<dd>
|
||||
<pre th:text="${additional}"></pre>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
64
webapp/lsp-manager/views/home.htm
Normal file
64
webapp/lsp-manager/views/home.htm
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<html layout:decorate="~{layout.htm}"
|
||||
xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
<head>
|
||||
|
||||
<title>Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content" class="container">
|
||||
<!-- have a button POST a click via AJAX -->
|
||||
<div style="display:inline-flex">
|
||||
<p th:text="${version}">ver</p>
|
||||
<sl-button hx-get="http://v2.jokeapi.dev/joke/Any?format=txt&safe-mode&type=single" hx-target="#joke-container" variant="default" hx-confirm="Do you want a Joke?">
|
||||
<sl-icon slot="prefix" name="emoji-laughing"></sl-icon>Joke
|
||||
</sl-button>
|
||||
<p id="joke-container" style="flex-grow:4"> </p>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<form hx-put="/pdf3/api/contact/1" hx-target="this" hx-swap="outerHTML">
|
||||
<div>
|
||||
<label>First Name</label>
|
||||
<input type="text" name="firstName" value="Joe"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Last Name</label>
|
||||
<input type="text" name="lastName" value="Blow"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email Address</label>
|
||||
<input type="email" name="email" value="joe@blow.com"/>
|
||||
</div>
|
||||
<button class="btn">Submit</button>
|
||||
<button class="btn" hx-get="/contact/1">Cancel</button>
|
||||
</form>
|
||||
<hr />
|
||||
<h2>Contacts</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="contacts-table" hx-get="/pdf3/api/contacts/table" hx-trigger="newContact from:body">
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Add A Contact</h2>
|
||||
<form hx-post="/pdf3/api/contacts">
|
||||
<label>
|
||||
Name
|
||||
<input name="name" type="text"/>
|
||||
</label>
|
||||
<label>
|
||||
Email
|
||||
<input name="email" type="email"/>
|
||||
</label>
|
||||
</form>
|
||||
<hr />
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
56
webapp/lsp-manager/views/job/home.htm
Normal file
56
webapp/lsp-manager/views/job/home.htm
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Jobs home</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class="container-fluid">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
|
||||
<a class="navbar-brand" href="#">Job</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a href="/app/jobs/_" class="nav-link">List</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9 mb-3 mb-sm-0">
|
||||
<div class="card ">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Something <span th:text="${count}" />
|
||||
,covers: <span th:text="${covers}" /></h5>
|
||||
<ul th:each="pdf: ${pdfs}">
|
||||
<li th:text="${pdf.slug}">aaa</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3 mb-3 mb-sm-0">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Job tasks</h5>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
28
webapp/lsp-manager/views/job/list.htm
Normal file
28
webapp/lsp-manager/views/job/list.htm
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Jobs</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class="container-fluid">
|
||||
<div class="d-flex ">
|
||||
<sl-breadcrumb class="flex-grow-1" >
|
||||
<sl-breadcrumb-item href="/app/jobs">Jobs home</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>jobs <span class="badge bg-secondary">4</span></sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
<sl-switch id="job-refresh" checked="checked" >Refresh</sl-switch>
|
||||
</div>
|
||||
|
||||
<h2>Jobs</h2>
|
||||
<hr />
|
||||
<div hx-get="/app/jobs/table" hx-trigger="every 1s [htmx.find('#job-refresh').checked]" >
|
||||
Nothing Yet!
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
157
webapp/lsp-manager/views/layout.htm
Normal file
157
webapp/lsp-manager/views/layout.htm
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="htmx-config"
|
||||
content='{
|
||||
"responseHandling":[
|
||||
{"code":"204", "swap": false},
|
||||
{"code":"[23]..", "swap": true},
|
||||
{"code":"404", "swap": true},
|
||||
{"code":"[45]..", "swap": false, "error":true},
|
||||
{"code":"...", "swap": true}
|
||||
],
|
||||
"selfRequestsOnly": false
|
||||
}'
|
||||
/>
|
||||
<title>MDUI</title>
|
||||
<link rel="icon" href="/app/static/favicon.png" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css"/>
|
||||
|
||||
<link rel="stylesheet" media="(prefers-color-scheme:light)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.0/cdn/themes/light.css" />
|
||||
<link rel="stylesheet" media="(prefers-color-scheme:dark)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.0/cdn/themes/dark.css"
|
||||
onload="document.documentElement.classList.add('sl-theme-dark');" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/shoelace.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/app/static/styles.css" />
|
||||
<script defer="defer" src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script defer="defer" src="/app/static/htmx/2.0.4/htmx.min.js"></script>
|
||||
<script defer="defer" src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js" integrity="sha256-y3ibfOyBqlgBd+GzwFYQEVOZdNJD06HeDXihongBXKs=" crossorigin="anonymous"></script>
|
||||
<script defer="defer" src="/app/static/script.js"></script>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body hx-boost="true" hx-indicator="#indicator" data-bs-theme="light" >
|
||||
<div class='App'>
|
||||
<header id="header">
|
||||
|
||||
<nav class="navbar navbar-expand-lg ">
|
||||
<div class="container-fluid">
|
||||
|
||||
<a class="navbar-brand" href="/app">MDUI</a>
|
||||
<sl-icon id="indicator" class="htmx-indicator" src="/app/static/loader.svg" ></sl-icon>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/app/home">Home</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/app/pdfs">pdf</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/app/jobs">jobs</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/app/dev">dev</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/app/tweets">tweets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/app/components">components</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/app/test">test</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<sl-input placeholder="search..." clearable="clearable" aria-label="Search">
|
||||
<sl-icon name="search" slot="prefix"></sl-icon>
|
||||
</sl-input>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/app/profile">
|
||||
<sl-avatar label="User avatar" style="--size: 2.0rem;"></sl-avatar>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/app/notifications">
|
||||
<sl-avatar label="User avatar" style="--size: 2.0rem;">
|
||||
<sl-icon slot="icon" name="bell"></sl-icon>
|
||||
</sl-avatar>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<sl-dropdown>
|
||||
<sl-avatar slot="trigger" label="theme" style="--size: 2.0rem;">
|
||||
<sl-icon slot="icon" name="circle-half"></sl-icon>
|
||||
</sl-avatar>
|
||||
|
||||
<sl-menu>
|
||||
<sl-menu-item> <sl-icon slot="prefix" name="sun-fill"></sl-icon>Light</sl-menu-item>
|
||||
<sl-menu-item> <sl-icon slot="prefix" name="moon-fill"></sl-icon>Dark</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item type="checkbox" checked="checked"> <sl-icon slot="prefix"
|
||||
name="circle-half"></sl-icon>System</sl-menu-item>
|
||||
|
||||
</sl-menu>
|
||||
</sl-dropdown>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</header>
|
||||
<main id="main" class="App-main">
|
||||
<p layout:fragment="content">MAIN</p>
|
||||
</main>
|
||||
<footer id="footer">
|
||||
<button onclick="toast('Hi. '+new Date())">toast</button>
|
||||
<sl-alert>hi</sl-alert>
|
||||
LoggedIn: ??
|
||||
<button type="button" class="btn btn-primary" id="liveToastBtn">Show live toast</button>
|
||||
|
||||
<sl-tooltip>
|
||||
<div id="server-timing" slot="content"/>
|
||||
<sl-icon name="stopwatch"></sl-icon>
|
||||
</sl-tooltip>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
<!-- bootstrap toast -->
|
||||
<div class="position-fixed bottom-0 end-0 p-3">
|
||||
<div id="toast" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">Bootstrap</strong>
|
||||
<small>? mins ago</small>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div id="toast-body" class="toast-body">detail here</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
25
webapp/lsp-manager/views/login.htm
Normal file
25
webapp/lsp-manager/views/login.htm
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Login</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<form onsubmit="${login}" action="${router.guardUrl()}">
|
||||
<fieldset style="width:50%;">
|
||||
<sl-input label="User name" value="${session.login}" oninput="${html.set(session, 'login')}"
|
||||
clearable="clearable"></sl-input>
|
||||
|
||||
<sl-input value="${session.password}" oninput="${html.set(session, 'password')}" type="password"
|
||||
label="Password" password-toggle="password-toggle" clearable="clearable"></sl-input>
|
||||
<input type="checkbox" checked="${session.loggedIn}" />
|
||||
</fieldset>
|
||||
<button type="submit">Login</button>
|
||||
<button onclick="${fastlogin}">Fast</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
15
webapp/lsp-manager/views/notifications.htm
Normal file
15
webapp/lsp-manager/views/notifications.htm
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Notifications</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class="container-fluid">
|
||||
<h1>Notifications</h1>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
78
webapp/lsp-manager/views/pdf/home.htm
Normal file
78
webapp/lsp-manager/views/pdf/home.htm
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>PDF home</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class="container-fluid">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
|
||||
<a class="navbar-brand" href="#">PDF</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a href="/app/pdfs/_" class="nav-link">List</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/app/pdfs/settings" class="nav-link">Settings</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9 mb-3 mb-sm-0">
|
||||
<div class="card ">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">PDF slugs <span th:text="${count}" />
|
||||
,covers: <span th:text="${covers}" /></h5>
|
||||
<ul th:each="pdf: ${pdfs}">
|
||||
<li th:text="${pdf.slug}">aaa</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3 mb-3 mb-sm-0">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">PDF tasks</h5>
|
||||
|
||||
<form layout="column items:start gap">
|
||||
|
||||
<div class="row mb-3">
|
||||
<button name="action" value="delete" hx-confirm="Delete all covers?" hx-post="/app/pdfs"
|
||||
hx-target="#aresponse">
|
||||
Delete covers
|
||||
</button>
|
||||
<span id="aresponse" />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="row mb-3">
|
||||
update covers
|
||||
<button name="action" value="update" hx-confirm="create all covers?" hx-post="/app/pdfs"
|
||||
hx-target="#bresponse">
|
||||
update covers
|
||||
</button>
|
||||
<span id="bresponse" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
16
webapp/lsp-manager/views/pdf/item-iframe.htm
Normal file
16
webapp/lsp-manager/views/pdf/item-iframe.htm
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>PDF-view</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<iframe th:src="@{/pdfs/_/{id}/raw}" src="/pdfs/_/{id}/raw" allowScripts="true"
|
||||
style="width:100%;height:80vh;overflow:clip;"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
60
webapp/lsp-manager/views/pdf/item.htm
Normal file
60
webapp/lsp-manager/views/pdf/item.htm
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>PDF</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class="container-fluid">
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item href="/app/pdfs">PDF</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item href="/app/pdfs/_">list </sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item><span th:text="${id}">#</span></sl-breadcrumb-item>
|
||||
|
||||
</sl-breadcrumb>
|
||||
<div class="card-group">
|
||||
<div class="card">
|
||||
<img th:src="@{/pdfs/_/{id}/cover}" src="" class="card-img-top object-fit-contain" alt="Image of cover page."
|
||||
style="height: 75vh;" />
|
||||
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Card title</h5>
|
||||
<p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional
|
||||
content. This card has even longer content than the first to show that equal height action.</p>
|
||||
<p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Tasks</h5>
|
||||
<div class="card-text">
|
||||
<div class="btn-group-vertical" role="group" aria-label="Button group with nested dropdown">
|
||||
<button type="button" class="btn btn-primary">1</button>
|
||||
<button type="button" class="btn btn-primary">2</button>
|
||||
|
||||
<div class="btn-group" role="group">
|
||||
<button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Dropdown
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||
<li><a class="dropdown-item" href="#">Dropdown link</a></li>
|
||||
<li><a class="dropdown-item" href="#">Dropdown link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
53
webapp/lsp-manager/views/pdf/list.htm
Normal file
53
webapp/lsp-manager/views/pdf/list.htm
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Pdfs</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class="container-fluid">
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item href="/app/pdfs">PDF</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>list (<span th:text="${count}">#</span>)</sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
|
||||
<div style="display:flex;flex-wrap:wrap;flex-direction:row;gap:10px;">
|
||||
|
||||
<sl-card th:each="pdf: ${pdfs}" class="card-overview">
|
||||
|
||||
<div slot="header">
|
||||
<sl-tooltip>
|
||||
<div th:text="${pdf.slug}" slot="content">path</div>
|
||||
<sl-badge th:text="${pdf.index}">{pdf.index}</sl-badge>
|
||||
</sl-tooltip>
|
||||
<sl-copy-button value="" th:value="${pdf.slug}" copy-label="copy PDF path">
|
||||
</sl-copy-button>
|
||||
<a th:href="@{/pdfs/_/{pdf.id}/raw}" href="/app/pdfs/_/{pdf.id}/details"><small>{pdf}</small></a>
|
||||
<sl-button-group label="History">
|
||||
<sl-icon-button name="file-earmark-pdf" label="Settings"
|
||||
th:href="@{/pdfs/_/{pdf.id}/view}" href="/app/pdfs/_/{pdf.id}/view"></sl-icon-button>
|
||||
|
||||
</sl-button-group>
|
||||
</div>
|
||||
<div>
|
||||
<a th:href="@{/pdfs/_/{pdf.id}}" href="/pdfs/_/{pdf.id}" class="holder center">
|
||||
<img th:src="@{/pdfs/_/{pdf.id}/cover}" src="" class="img-thumbnail object-fit-contain" loading="lazy"
|
||||
alt="Image of cover page." style="width:200px;height: 300px;"/>
|
||||
</a>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
|
||||
<div>
|
||||
slug:<br /><small th:text="${pdf.id}">{pdf.id}</small><br />
|
||||
</div>
|
||||
</div>
|
||||
</sl-card>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
97
webapp/lsp-manager/views/pdf/settings.htm
Normal file
97
webapp/lsp-manager/views/pdf/settings.htm
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Settings</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid" layout:fragment="content">
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item href="/app/pdfs">PDF</sl-breadcrumb-item>
|
||||
<sl-breadcrumb-item>settings</sl-breadcrumb-item>
|
||||
</sl-breadcrumb>
|
||||
<form method="post" action="/app/pdfs/settings" layout="column items:start gap" class="input-validation-required container">
|
||||
<fieldset>
|
||||
<legend>Settings</legend>
|
||||
<div class="row mb-3">
|
||||
<label for="inputdp" class="col-sm-2 col-form-label text-end">dataPath</label>
|
||||
<div class="col-sm-8">
|
||||
<sl-input id="inputdp" name="dataPath"
|
||||
clearable="clearable" filled="filled" required="required" value="{settings.dataPath}"
|
||||
th:value="${ settings.dataPath }"
|
||||
help-text="Root folder on server containing data files e.g.'/data/'"></sl-input>
|
||||
<div th:text="${error.dataPath}" class='error-message'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="row mb-3 ">
|
||||
<label for="inputdrop" class="col-sm-2 col-form-label text-end">drop</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<sl-input id="inputdrop" name="drop"
|
||||
clearable="clearable" filled="filled"
|
||||
value="{settings.drop}" th:value="${ settings.drop }" help-text="optional sub-folder of dataPath e.g. drop-01e/"></sl-input>
|
||||
<div th:text="${error.drop}" class='error-message'></div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="selectam" class="col-sm-2 col-form-label text-end">Animation</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<sl-select id="selectam" name="animation" value="{settings.animation}" th:value="${ settings.animation }"
|
||||
filled="filled" help-text="Animation for page transistions.">
|
||||
<sl-option value="">None</sl-option>
|
||||
<sl-option value="fade">Fade</sl-option>
|
||||
<sl-option value="slide-fade">slide-fade</sl-option>
|
||||
<sl-option value="bounce">bounce</sl-option>
|
||||
</sl-select>
|
||||
<div class='error-message'></div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
</fieldset>
|
||||
<div class="mx-auto" style="width: 600px;">
|
||||
<sl-button type="submit" variant="primary">Save</sl-button>
|
||||
<sl-button type="reset">Reset to default values</sl-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.errors {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.label-on-left {
|
||||
--label-width: 8rem;
|
||||
--gap-width: 1rem;
|
||||
}
|
||||
|
||||
.label-on-left+.label-on-left {
|
||||
margin-top: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.label-on-left::part(form-control) {
|
||||
display: grid;
|
||||
grid: auto / var(--label-width) 1fr;
|
||||
gap: var(--sl-spacing-3x-small) var(--gap-width);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label-on-left::part(form-control-label) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.label-on-left::part(form-control-help-text) {
|
||||
grid-column-start: 2;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
17
webapp/lsp-manager/views/profile.htm
Normal file
17
webapp/lsp-manager/views/profile.htm
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>Profile</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content" class='ProfileIndex'>
|
||||
<h2>{uid} profile</h2>
|
||||
<hr />
|
||||
<hr />
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
42
webapp/lsp-manager/views/test.htm
Normal file
42
webapp/lsp-manager/views/test.htm
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>test</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<sl-card class="card-overview">
|
||||
|
||||
<div slot="header">
|
||||
<sl-tooltip>
|
||||
<div th:text="${pdf.slug}" slot="content">path</div>
|
||||
<sl-badge th:text="${pdf.index}">{pdf.index}</sl-badge>
|
||||
</sl-tooltip>
|
||||
|
||||
<a th:href="@{/pdfs/{pdf.id}/raw}" href="/app/pdfs/{pdf.id}/details"><small>{pdf}</small></a>
|
||||
<sl-button-group label="History">
|
||||
<sl-icon-button name="file-earmark-pdf" label="Settings"
|
||||
th:href="@{/pdfs/{pdf.id}/raw}" href="/app/pdfs/{pdf.id}/view"></sl-icon-button>
|
||||
|
||||
</sl-button-group>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;">
|
||||
<div>
|
||||
<a href="/app/pdfs/{pdf.id}/details" class="holder center">
|
||||
<img src="/app/pdfs/{pdf.id}/cover" loading="{index >10?'lazy':'eager'}"
|
||||
alt="A kitten sits patiently." />
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
slug:<br /><small th:text="${pdf.id}">{pdf.id}</small><br />
|
||||
</div>
|
||||
</div>
|
||||
</sl-card>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
67
webapp/lsp-manager/views/tweets.htm
Normal file
67
webapp/lsp-manager/views/tweets.htm
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout.htm}"
|
||||
xmlns:th="http://www.thymeleaf.org" >
|
||||
<head>
|
||||
|
||||
<title>Tweets</title>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content" class='Home' >
|
||||
<h2>Tweets</h2>
|
||||
<div class='Tweet'>
|
||||
<div class='Tweet-author'>
|
||||
<a href="/app/profile/{uid}"
|
||||
th:href="@{/profile/{uid}(uid='dan_abramov')}">Dan Abramov @dan_abramov</a>
|
||||
</div>
|
||||
<div class='Tweet-time'>
|
||||
<sl-relative-time date="2020-07-15T09:17:00-04:00">2020-07-15T09:17:00-04:00</sl-relative-time>
|
||||
</div>
|
||||
<div class='Tweet-content'>Another use case for 'this.context' I think might be valid: forms. They're too painful right now.</div>
|
||||
</div>
|
||||
<div class='Tweet'>
|
||||
<div class='Tweet-author'>
|
||||
<a href="/app/profile/{uid}"
|
||||
th:href="@{profile/{uid}(uid=afanasjevas)}"
|
||||
>Eduardas Afanasjevas @afanasjevas</a>
|
||||
</div>
|
||||
<div class='Tweet-time'>
|
||||
<sl-relative-time date="2024-04-15T09:17:00-04:00">2024-04-15T09:17:00-04:00</sl-relative-time></div>
|
||||
<div class='Tweet-content'>I just published “What will Datasmoothie bring to the analytics startup landscape?” https://medium.com/@afanasjevas/what-will-datasmoothie-bring-to-the-analytics-startup-landscape-f7dab70d75c3?source=tw-81c4e81fe6f8-1427630532296</div>
|
||||
</div>
|
||||
<div class='Tweet'>
|
||||
<div class='Tweet-author'>
|
||||
<a href="/app/profile/{uid}"
|
||||
th:href="@{profile/{uid}(uid=LNUGorg)}"
|
||||
>LNUG @LNUGorg</a>
|
||||
</div>
|
||||
<div class='Tweet-time'>52m52 minutes ago</div>
|
||||
<div class='Tweet-content'> new talks uploaded on our YouTube page - check them out http://bit.ly/1yoXSAO</div>
|
||||
</div>
|
||||
<style>
|
||||
|
||||
.Tweet {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.Tweet:last-child {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.Tweet-author {
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.Tweet-time {
|
||||
color: #888;
|
||||
display: inline-block;
|
||||
margin-left: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
19
webapp/lsp-manager/views/unknown.htm
Normal file
19
webapp/lsp-manager/views/unknown.htm
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE HTML5>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout.htm}">
|
||||
|
||||
<head>
|
||||
<title>404</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<h2>Page not found:</h2>
|
||||
<ul>
|
||||
<li th:text="${path}">Page: pdf2/${ path }</li>
|
||||
<li>Method: ${ method }</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
266
webapp/lsp/etc/snippets.json
Normal file
266
webapp/lsp/etc/snippets.json
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
{
|
||||
"new library module": {
|
||||
"isFileTemplate": true,
|
||||
"prefix": "library module",
|
||||
"body": [
|
||||
"xquery version '3.1';",
|
||||
"(:~",
|
||||
"@author: ",
|
||||
"@date: $CURRENT_YEAR/$CURRENT_MONTH/$CURRENT_DATE",
|
||||
":)",
|
||||
"module namespace ${1:prefix} = '${2:http://www.example.com/}';",
|
||||
""
|
||||
],
|
||||
"description": "New library module template"
|
||||
},
|
||||
"new main module": {
|
||||
"isFileTemplate": true,
|
||||
"prefix": "main module",
|
||||
"body": [
|
||||
"xquery version '3.1';",
|
||||
"(:~",
|
||||
":)",
|
||||
"${1:expr}",
|
||||
""
|
||||
],
|
||||
"description": "New main module template"
|
||||
},
|
||||
"flowr": {
|
||||
"prefix": [
|
||||
"for",
|
||||
"flowr"
|
||||
],
|
||||
"body": [
|
||||
"for \\$${1:var} at \\$${2:pos} in ${3:expr}",
|
||||
"let \\$${4:var2} := ${5:expr}",
|
||||
"where ${6:boolean}",
|
||||
"order by ${7:expr}",
|
||||
"return ${8:expr2}"
|
||||
],
|
||||
"description": "Full FLOWR expression"
|
||||
},
|
||||
"return": {
|
||||
"prefix": "return",
|
||||
"body": "return ${1:expr}"
|
||||
},
|
||||
"import": {
|
||||
"prefix": "import",
|
||||
"body": "import module namespace ${1:ns} = '${2:http://www.example.com/}';",
|
||||
"description": "Import module"
|
||||
},
|
||||
"if": {
|
||||
"prefix": "if",
|
||||
"body": [
|
||||
"if (${1:boolean})",
|
||||
"then ${2:expr1}",
|
||||
"else ${3:expr2}"
|
||||
],
|
||||
"description": "If then else expression"
|
||||
},
|
||||
"module": {
|
||||
"prefix": "module",
|
||||
"body": "module namespace ${1:ns} = '${2:http://www.example.com}';"
|
||||
},
|
||||
"every": {
|
||||
"prefix": "every",
|
||||
"body": "every \\$${1:varname} in ${2:expr} satisfies ${3:expr}"
|
||||
},
|
||||
"some": {
|
||||
"prefix": "some",
|
||||
"body": "some \\$${1:varname} in ${2:expr} satisfies ${3:expr}"
|
||||
},
|
||||
"declare namespace": {
|
||||
"prefix": [
|
||||
"declare",
|
||||
"namespace"
|
||||
],
|
||||
"body": [
|
||||
"declare ${1:prefix}='${2:namespace}';",
|
||||
""
|
||||
],
|
||||
"description": "declare namespace"
|
||||
},
|
||||
"declare base-uri": {
|
||||
"prefix": [
|
||||
"declare",
|
||||
"baseuri"
|
||||
],
|
||||
"body": [
|
||||
"declare base-uri '${1:uriliteral}';",
|
||||
""
|
||||
],
|
||||
"description": "declare base-uri"
|
||||
},
|
||||
"declare option": {
|
||||
"prefix": [
|
||||
"declare",
|
||||
"option"
|
||||
],
|
||||
"body": [
|
||||
"declare option ${1:eqname} '${2:string}';",
|
||||
""
|
||||
],
|
||||
"description": "declare option"
|
||||
},
|
||||
"declare function": {
|
||||
"prefix": [
|
||||
"declare",
|
||||
"function"
|
||||
],
|
||||
"body": [
|
||||
"(:~ ${1:name} :)",
|
||||
"declare function ${2:ns}:${1:name}()",
|
||||
"as ${3:type}{",
|
||||
"${3:expr}",
|
||||
"};",
|
||||
""
|
||||
],
|
||||
"description": "declare function"
|
||||
},
|
||||
"declare variable": {
|
||||
"prefix": [
|
||||
"declare",
|
||||
"variable"
|
||||
],
|
||||
"body": [
|
||||
"(:~ \\$${1:varname} :)",
|
||||
"declare variable \\$${1:varname} := ${2:expr};",
|
||||
""
|
||||
],
|
||||
"description": "declare variable"
|
||||
},
|
||||
"switch": {
|
||||
"prefix": "switch",
|
||||
"body": [
|
||||
"switch(${1:foo})",
|
||||
"case ${2:foo} return ${3:true}",
|
||||
"default return ${4:false}"
|
||||
],
|
||||
"description": "switch statement"
|
||||
},
|
||||
"typeswitch": {
|
||||
"prefix": "type",
|
||||
"body": [
|
||||
"typeswitch(${1:foo})",
|
||||
"case ${2:foo} return ${3:true}",
|
||||
"default return ${4:false}"
|
||||
],
|
||||
"description": "typeswitch statement"
|
||||
},
|
||||
"try": {
|
||||
"prefix": "try",
|
||||
"body": [
|
||||
"try {",
|
||||
" ${1:expr}",
|
||||
"} catch ${2:*}",
|
||||
" { ${3:expr}",
|
||||
"}"
|
||||
],
|
||||
"description": "try catch"
|
||||
},
|
||||
"tumbling": {
|
||||
"prefix": [
|
||||
"for",
|
||||
"tumbling",
|
||||
"window"
|
||||
],
|
||||
"body": [
|
||||
"for tumbling window \\$${1:varname} in ${2:expr}",
|
||||
"start at \\$${3:start} when ${4:expr}",
|
||||
"end at \\$${5:end} when ${6:expr}",
|
||||
"return ${7:expr}"
|
||||
],
|
||||
"description": "tumbling window"
|
||||
},
|
||||
"sliding": {
|
||||
"prefix": [
|
||||
"for",
|
||||
"sliding",
|
||||
"window"
|
||||
],
|
||||
"body": [
|
||||
"for sliding window \\$${1:varname} in ${2:expr}",
|
||||
"start at \\$${3:start} when ${4:expr}",
|
||||
"end at \\$${5:end} when ${6:expr}",
|
||||
"return ${7:expr}"
|
||||
],
|
||||
"description": "sliding window"
|
||||
},
|
||||
"let": {
|
||||
"prefix": "let",
|
||||
"body": "let \\$${1:varname} := ${2:expr}"
|
||||
},
|
||||
"castable": {
|
||||
"body": "castable as ${1:atomicType}"
|
||||
},
|
||||
"cast": {
|
||||
"body": "cast as ${1:atomicType}"
|
||||
},
|
||||
|
||||
"update insert": {
|
||||
"prefix": [
|
||||
"update",
|
||||
"insert"
|
||||
],
|
||||
"body": "insert node ${1:expr} into ${2:xpath}"
|
||||
},
|
||||
"update delete": {
|
||||
"prefix": ["delete","update"],
|
||||
"body": "delete node ${1:xpath}"
|
||||
},
|
||||
"update replace node": {
|
||||
"prefix":["update","replace"],
|
||||
"body": "replace node ${1:xpath} with ${2:expr}"
|
||||
},
|
||||
"update replace value": {
|
||||
"prefix": [ "update",
|
||||
"replace",
|
||||
"value"
|
||||
],
|
||||
"body": "replace value of node ${1:xpath} with ${2:expr}"
|
||||
},
|
||||
"update rename": {
|
||||
"prefix": [
|
||||
"update",
|
||||
"rename"
|
||||
],
|
||||
"body": "rename node ${1:xpath} as ${2:eqname}"
|
||||
},
|
||||
"copy modify return": {
|
||||
"prefix": [
|
||||
"copy",
|
||||
"modify",
|
||||
"return"
|
||||
],
|
||||
"body": [
|
||||
"copy \\$${1:varname} := ${2:node}",
|
||||
"modify ${3:updates}",
|
||||
"return \\$${1:varname}"
|
||||
]
|
||||
},
|
||||
"transform with": {
|
||||
"prefix": [
|
||||
"transform",
|
||||
"with",
|
||||
"update"
|
||||
],
|
||||
"body": [
|
||||
"${1:node} transform with {",
|
||||
" ${2:update}",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"transform update": {
|
||||
"prefix": [
|
||||
"transform",
|
||||
"update"
|
||||
],
|
||||
"body": [
|
||||
"${1:node} update {",
|
||||
"${2:update}",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -50,9 +50,9 @@ declare
|
|||
function lsp-text:completion($json as map(*))
|
||||
as map(*)?
|
||||
{
|
||||
let $doc:=$json?params?textDocument?uri
|
||||
let $uri:=$json?params?textDocument?uri
|
||||
let $context:=$json?params?context (:{"triggerCharacter":":","triggerKind":2.0e0}:)
|
||||
let $result:=comp:list($context)
|
||||
let $result:=comp:list($context)=>prof:time("⏱️ completions " || $uri)
|
||||
return rpc:result($json,array:build($result))
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ module namespace comp = 'lsp-completions';
|
|||
import module namespace lspt = 'lsp-typedefs' at "../lsp-typedefs.xqm";
|
||||
import module namespace ctx="lsp/context" at "../context.xqm";
|
||||
|
||||
declare variable $comp:snippet-source:="../etc/snippets.json";
|
||||
declare variable $comp:snippets:=json:doc($comp:snippet-source,{"format":"w3"});
|
||||
|
||||
(: (:{"triggerCharacter":":","triggerKind":2.0e0}:):)
|
||||
declare function comp:list($context as map(*))
|
||||
as lspt:CompletionItem*
|
||||
{
|
||||
message($context,"context: "),
|
||||
ctx:functions("fn")!ctx:map(.)=>trace("aaa")
|
||||
ctx:functions("fn")!ctx:map(.)
|
||||
};
|
||||
|
||||
declare function comp:dummy($context as map(*))
|
||||
|
|
|
|||
|
|
@ -5,17 +5,18 @@ import module namespace hnd="lsp/handlers" at "../handlers.xqm";
|
|||
import module namespace lspt = 'lsp-typedefs' at "../lsp-typedefs.xqm";
|
||||
import module namespace pos="lsp/position" at "../position.xqm";
|
||||
|
||||
declare function syms:list($parse as element(),$text as xs:string)
|
||||
as lspt:DocumentSymbol*{
|
||||
let $actions as hnd:actionMap :={
|
||||
declare variable $syms:actions as hnd:actionMap :={
|
||||
"ContextValueDecl": syms:action#2,
|
||||
"VarDecl": syms:VarDecl#2,
|
||||
"FunctionDecl": syms:FunctionDecl#2,
|
||||
"ItemTypeDecl": syms:action#2,
|
||||
"NamedRecordTypeDecl": syms:action#2
|
||||
}
|
||||
"NamedRecordTypeDecl": syms:NamedRecordTypeDecl#2
|
||||
};
|
||||
|
||||
declare function syms:list($parse as element(),$text as xs:string)
|
||||
as lspt:DocumentSymbol*{
|
||||
let $state:= hnd:Result(())
|
||||
let $result:= hnd:walk($parse,$actions,$state)
|
||||
let $result:= hnd:walk($parse,$syms:actions,$state)
|
||||
return $result?result
|
||||
};
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ as hnd:Result{
|
|||
let $name:=syms:localName($parse/VarNameAndType/EQName)
|
||||
let $length:=string($parse)=>string-length()
|
||||
let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3))
|
||||
let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Variable'),$range,$range,"TODO")
|
||||
let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Variable'),$range,$range,"VAR")
|
||||
return ($state?result,$sym)=>hnd:Result(true())
|
||||
};
|
||||
|
||||
|
|
@ -50,7 +51,16 @@ declare function syms:FunctionDecl($parse as element(FunctionDecl),$state as hn
|
|||
as hnd:Result{
|
||||
let $name:=syms:localName($parse/UnreservedFunctionEQName)
|
||||
let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3))
|
||||
let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Method'),$range,$range,"TODO")
|
||||
let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Method'),$range,$range,"FUN")
|
||||
return ($state?result,$sym)=>hnd:Result(true())
|
||||
};
|
||||
|
||||
declare function syms:NamedRecordTypeDecl($parse as element(NamedRecordTypeDecl), $state as hnd:Result )
|
||||
as hnd:Result{
|
||||
let $name:=syms:localName($parse/EQName)
|
||||
let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3))
|
||||
let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('TypeParameter'),$range,$range,"--RECORD")
|
||||
=>trace("RECORD")
|
||||
return ($state?result,$sym)=>hnd:Result(true())
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,12 @@ form header {
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
details[open]::details-content {
|
||||
color: dodgerblue;
|
||||
padding: 0.1em;
|
||||
border: thin solid grey;
|
||||
overflow:auto;
|
||||
}
|
||||
::backdrop {
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
|
@ -78,6 +84,7 @@ form header {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.page-header {
|
||||
grid-column: 1 / -1;
|
||||
background: #ffcdd2;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<a class="nav-link active" aria-current="page" href="#">Editor</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">LSP UI</a>
|
||||
<a class="nav-link" href="/app/home" target="lsp">LSP Manager</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/dba/logs" target="dba">Dba</a>
|
||||
|
|
@ -57,7 +57,11 @@
|
|||
generator.xquery</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<button popovertarget="popAbout" type="button" class="btn btn-light">
|
||||
<i class="codicon codicon-info"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</header>
|
||||
<nav class="page-nav">
|
||||
|
|
@ -144,8 +148,8 @@
|
|||
<option value="xquery">xquery</option>
|
||||
<option value="xml">xml</option>
|
||||
</select>
|
||||
<button popovertarget="popHelp"><i class="codicon codicon-info"></i></button>
|
||||
<button id="cmd" type="button" class="btn btn-light" title="Command and key mapping help">
|
||||
|
||||
<button id="cmdList" type="button" class="btn btn-light" title="Command and key mapping help">
|
||||
<i class="codicon codicon-record-keys"></i>
|
||||
</button>
|
||||
</footer>
|
||||
|
|
@ -167,21 +171,31 @@
|
|||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog id="popHelp" popover>
|
||||
<dialog id="popCmds" popover>
|
||||
<form>
|
||||
<header>Commands and keys
|
||||
<button type="button" class="btn-close" aria-label="Close"
|
||||
onclick="$('popHelp').hidePopover(); "></button>
|
||||
onclick="$('popCmds').hidePopover(); "></button>
|
||||
</header>
|
||||
<div id="popHelpInfo" class="modal-body" style="height: 50vh;overflow:scroll;">
|
||||
TODO
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="popAbout" popover>
|
||||
<form>
|
||||
<header>Help</header>
|
||||
<div class="modal-body" style="height: 50vh;overflow:scroll;">
|
||||
<p>TODO help info</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<!-- <popup-info id="popHelp">hhhh</popup-info> -->
|
||||
|
||||
<dialog id="popSettings" popover>
|
||||
|
|
|
|||
|
|
@ -85,15 +85,15 @@ $("symbols2").onclick = e => {
|
|||
});
|
||||
};
|
||||
|
||||
$("cmd").onclick = e => {
|
||||
$("cmdList").onclick = e => {
|
||||
let cmds = lsp.listCommands(view);
|
||||
let result="";
|
||||
[...cmds.keys()].sort().forEach(key => {
|
||||
result+=`<li>${key} ${cmds.get(key).key}</li>`
|
||||
});
|
||||
$("popHelpInfo").innerHTML=`<ul>${result}</ul>`
|
||||
$("popHelp").showPopover()
|
||||
console.log(result)
|
||||
$("popCmds").showPopover()
|
||||
|
||||
};
|
||||
|
||||
$("symList").addEventListener("itemSelected", e => {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class ListComponent extends HTMLElement {
|
|||
/* Render the list items #data */
|
||||
render() {
|
||||
const list = document.createElement('ul');
|
||||
list.style = "max-height:80cqh;overflow: auto;";
|
||||
list.style = "max-height:45cqh;overflow: auto;";
|
||||
|
||||
const select = e => {
|
||||
if(e.type ==="keyup" && !(e.key==="Enter" )) return;
|
||||
|
|
@ -65,7 +65,7 @@ class ListComponent extends HTMLElement {
|
|||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@import url("../codicon@0.0.40/codicon.css");
|
||||
ul { list-style-type: none; padding:0;margin:0;max-height:80cqh;overflow: auto;
|
||||
ul { list-style-type: none; padding:0;margin:0;overflow: auto;
|
||||
background-color: #f8f9fa;font-size: 80%;}
|
||||
li { padding: 0 0 0 2px; border-bottom: 1px solid #ccc; cursor: pointer; width:100%; }
|
||||
li:not(.selected) :hover { background-color: #ccc; }
|
||||
|
|
@ -83,4 +83,88 @@ class ListComponent extends HTMLElement {
|
|||
}
|
||||
|
||||
/* Define the new custom element */
|
||||
customElements.define('qd-list', ListComponent);
|
||||
customElements.define('qd-list', ListComponent);
|
||||
|
||||
class PanelComponent extends HTMLElement {
|
||||
#shadow;
|
||||
#data;
|
||||
#iconKinds; //array from kind integer to codicon name
|
||||
constructor() {
|
||||
super();
|
||||
this.#shadow = this.attachShadow({ mode: "open", delegatesFocus: true });
|
||||
this.#data = [];
|
||||
this.#iconKinds = [];
|
||||
this.render();
|
||||
}
|
||||
|
||||
setData(newData, icons, append = false) {
|
||||
if (!Array.isArray(newData)) {
|
||||
console.warn("Invalid format, expected an array.");
|
||||
return;
|
||||
}
|
||||
this.#iconKinds = icons;
|
||||
this.#data = append ? this.#data.concat(newData) : structuredClone(newData);
|
||||
this.render();
|
||||
}
|
||||
|
||||
set data(value) {
|
||||
this.setData(value, false);
|
||||
}
|
||||
get data() {
|
||||
return this.#data;
|
||||
}
|
||||
|
||||
/* Render the list items #data */
|
||||
render() {
|
||||
const list = document.createElement('ul');
|
||||
list.style = "max-height:45cqh;overflow: auto;";
|
||||
|
||||
const select = e => {
|
||||
if(e.type ==="keyup" && !(e.key==="Enter" )) return;
|
||||
this.#shadow.querySelectorAll('li.selected').forEach(item => { item.className = ''; });
|
||||
e.currentTarget.className = 'selected';
|
||||
const i=e.currentTarget.getAttribute("data-index")
|
||||
console.log('Item index clicked:', i,this.#data[i]);
|
||||
// You can dispatch a custom event here if needed.
|
||||
this.dispatchEvent(new CustomEvent('itemSelected', {
|
||||
detail: this.#data[i],
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
};
|
||||
this.#data.forEach((item, index) => {
|
||||
const listItem = document.createElement('li');
|
||||
listItem.setAttribute("tabindex", "0")
|
||||
listItem.setAttribute("data-index", index)
|
||||
listItem.innerHTML = `<i class='codicon codicon-${this.#iconKinds[item.kind]}'></i>
|
||||
<span >${item.name} - ${item.detail}</span>`;
|
||||
|
||||
listItem.addEventListener('click', select);
|
||||
listItem.addEventListener('keyup', select);
|
||||
list.appendChild(listItem);
|
||||
});
|
||||
|
||||
this.#shadow.innerHTML = '';
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@import url("../codicon@0.0.40/codicon.css");
|
||||
ul { list-style-type: none; padding:0;margin:0;overflow: auto;
|
||||
background-color: #f8f9fa;font-size: 80%;}
|
||||
li { padding: 0 0 0 2px; border-bottom: 1px solid #ccc; cursor: pointer; width:100%; }
|
||||
li:not(.selected) :hover { background-color: #ccc; }
|
||||
.selected { background-color: #0d6efd;color: #ffff;}
|
||||
i {vertical-align: middle;}
|
||||
`;
|
||||
this.#shadow.appendChild(style);
|
||||
this.#shadow.appendChild(list);
|
||||
}
|
||||
|
||||
/* Render an error message if JSON fetching fails */
|
||||
renderError(error) {
|
||||
this.#shadow.innerHTML = `<p>Error loading data: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
/* Define the new custom element */
|
||||
customElements.define('qd-panel', PanelComponent);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue