diff --git a/CHANGELOG.md b/CHANGELOG.md index 53eebe2..8765504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # 0.16.1 * fix: fix `QueryBuilderAdapter` error when WHERE statement is empty +* chore: update demo # 0.16.0 diff --git a/README.md b/README.md index 674f504..d074423 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,13 @@ your data. ### Queries -This library supports queries with multiple sort columns. +Multiple sort columns are supported. The library will automatically generate the +WHERE query for you, including the complex cases involving more than two sort +columns. The only requirement is that the query needs to have a deterministic +sort order. -The required query for performing keyset pagination is complex, especially if -more than one column is used for sorting. This library handles that task -automatically. The only requirement is that the query needs to have a -deterministic sort order. +Some backends also have the option to use SQL row values syntax for a slightly +better performance. ### Bidirectional Navigation and Page Skipping diff --git a/tests/public/prism.css b/tests/public/prism.css index b38a102..0253c16 100644 --- a/tests/public/prism.css +++ b/tests/public/prism.css @@ -1,3 +1,182 @@ /* PrismJS 1.29.0 -https://prismjs.com/download.html#themes=prism&languages=markup+markup-templating+php */ -code[class*=language-],pre[class*=language-]{color:#000;font-weight:500;background:0 0;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-]:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} +https://prismjs.com/download.html#themes=prism-coy&languages=markup+markup-templating+php+sql */ +code[class*=language-], +pre[class*=language-] { + color: #000; + background: 0 0; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre-wrap; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none +} + +pre[class*=language-] { + position: relative; + margin: .5em 0; + overflow: visible; + padding: 1px +} + +pre[class*=language-]>code { + position: relative; + z-index: 1; + border-left: 10px solid #358ccb; + box-shadow: -1px 0 0 0 #358ccb, 0 0 0 1px #dfdfdf; + background-color: #fdfdfd; + background-size: 3em 3em; + background-origin: content-box; + background-attachment: local +} + +code[class*=language-] { + max-height: inherit; + height: inherit; + padding: 0 1em; + display: block; + overflow: auto +} + +:not(pre)>code[class*=language-], +pre[class*=language-] { + background-color: #fdfdfd; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-bottom: 1em +} + +:not(pre)>code[class*=language-] { + position: relative; + padding: .2em; + border-radius: .3em; + color: #c92c2c; + border: 1px solid rgba(0, 0, 0, .1); + display: inline; + white-space: normal +} + +.token.block-comment, +.token.cdata, +.token.comment, +.token.doctype, +.token.prolog { + color: #7d8b99 +} + +.token.punctuation { + color: #5f6364 +} + +.token.boolean, +.token.constant, +.token.deleted, +.token.function-name, +.token.number, +.token.property, +.token.symbol, +.token.tag { + color: #c92c2c +} + +.token.attr-name, +.token.builtin, +.token.char, +.token.function, +.token.inserted, +.token.selector, +.token.string { + color: #2f9c0a +} + +.token.entity, +.token.operator, +.token.url, +.token.variable { + color: #a67f59; + background: rgba(255, 255, 255, .5) +} + +.token.atrule, +.token.attr-value, +.token.class-name, +.token.keyword { + color: #1990b8 +} + +.token.important, +.token.regex { + color: #e90 +} + +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: rgba(255, 255, 255, .5) +} + +.token.important { + font-weight: 400 +} + +.token.bold { + font-weight: 700 +} + +.token.italic { + font-style: italic +} + +.token.entity { + cursor: help +} + +.token.namespace { + opacity: .7 +} + +@media screen and (max-width:767px) { + + pre[class*=language-]:after, + pre[class*=language-]:before { + bottom: 14px; + box-shadow: none + } +} + +pre[class*=language-].line-numbers.line-numbers { + padding-left: 0 +} + +pre[class*=language-].line-numbers.line-numbers code { + padding-left: 3.8em +} + +pre[class*=language-].line-numbers.line-numbers .line-numbers-rows { + left: 0 +} + +pre[class*=language-][data-line] { + padding-top: 0; + padding-bottom: 0; + padding-left: 0 +} + +pre[data-line] code { + position: relative; + padding-left: 4em +} + +pre .line-highlight { + margin-top: 0 +} \ No newline at end of file diff --git a/tests/public/prism.js b/tests/public/prism.js index 61cf8da..ad72fa8 100644 --- a/tests/public/prism.js +++ b/tests/public/prism.js @@ -1,6 +1,7 @@ /* PrismJS 1.29.0 -https://prismjs.com/download.html#themes=prism&languages=markup+markup-templating+php */ +https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+markup-templating+php+sql */ var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; !function(e){function n(e,n){return"___"+e.toUpperCase()+n+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(t,a,r,o){if(t.language===a){var c=t.tokenStack=[];t.code=t.code.replace(r,(function(e){if("function"==typeof o&&!o(e))return e;for(var r,i=c.length;-1!==t.code.indexOf(r=n(a,i));)++i;return c[i]=e,r})),t.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(t,a){if(t.language===a&&t.tokenStack){t.grammar=e.languages[a];var r=0,o=Object.keys(t.tokenStack);!function c(i){for(var u=0;u=o.length);u++){var g=i[u];if("string"==typeof g||g.content&&"string"==typeof g.content){var l=o[r],s=t.tokenStack[l],f="string"==typeof g?g:g.content,p=n(a,l),k=f.indexOf(p);if(k>-1){++r;var m=f.substring(0,k),d=new e.Token(a,e.tokenize(s,t.grammar),"language-"+a,s),h=f.substring(k+p.length),v=[];m&&v.push.apply(v,c([m])),v.push(d),h&&v.push.apply(v,c([h])),"string"==typeof g?i.splice.apply(i,[u,1].concat(v)):g.content=v}}else g.content&&c(g.content)}return i}(t.tokens)}}}})}(Prism); !function(e){var a=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,t=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],i=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,n=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,s=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:a,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:t,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:i,operator:n,punctuation:s};var l={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},r=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:l}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:l}}];e.languages.insertBefore("php","variable",{string:r,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:a,string:r,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:t,number:i,operator:n,punctuation:s}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(a){/<\?/.test(a.code)&&e.languages["markup-templating"].buildPlaceholders(a,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)})),e.hooks.add("after-tokenize",(function(a){e.languages["markup-templating"].tokenizePlaceholders(a,"php")}))}(Prism); +Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}; diff --git a/tests/src/App/Controller/DemoController.php b/tests/src/App/Controller/DemoController.php index d51a0ff..131b47e 100644 --- a/tests/src/App/Controller/DemoController.php +++ b/tests/src/App/Controller/DemoController.php @@ -13,36 +13,59 @@ namespace Rekalogika\Rekapager\Tests\App\Controller; +use Doctrine\ORM\EntityManagerInterface; +use Rekalogika\Contracts\Rekapager\PageableInterface; use Rekalogika\Rekapager\Bundle\Contracts\PagerFactoryInterface; use Rekalogika\Rekapager\Bundle\PagerOptions; use Rekalogika\Rekapager\Tests\App\Contracts\PageableGeneratorInterface; use Rekalogika\Rekapager\Tests\App\Doctrine\SqlLogger; +use Rekalogika\Rekapager\Tests\App\Entity\Post; use Rekalogika\Rekapager\Tests\App\Form\PagerParameters; use Rekalogika\Rekapager\Tests\App\Form\PagerParametersType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; +use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; /** @psalm-suppress PropertyNotSetInConstructor */ class DemoController extends AbstractController { + /** + * @var array> + */ + private readonly array $pageableGenerators; + /** * @param iterable> $pageableGenerators * @psalm-suppress DeprecatedClass */ public function __construct( - #[TaggedIterator('rekalogika.rekapager.pageable_generator', defaultIndexMethod: 'getKey')] - private readonly iterable $pageableGenerators, + #[AutowireIterator('rekalogika.rekapager.pageable_generator', defaultIndexMethod: 'getKey')] + iterable $pageableGenerators ) { + /** + * @psalm-suppress InvalidArgument + * @psalm-suppress MixedPropertyTypeCoercion + */ + $this->pageableGenerators = iterator_to_array($pageableGenerators); + } + + #[Route('/', name: 'index')] + public function index(): Response + { + return $this->render('app/index.html.twig', [ + 'pageable_generators' => $this->pageableGenerators, + ]); } /** * @param PagerFactoryInterface $pagerFactory */ - #[Route('/{key?}', name: 'rekapager')] - public function index( + #[Route('/page/{key?}', name: 'page')] + public function page( Request $request, SqlLogger $logger, PagerFactoryInterface $pagerFactory, @@ -53,27 +76,7 @@ public function index( /** @var PagerParameters */ $pagerParameters = $form->getData(); - - /** @psalm-suppress InvalidArgument */ - $pageableGenerators = iterator_to_array($this->pageableGenerators); - - /** @var array> $pageableGenerators */ - - if ($key === null) { - foreach ($pageableGenerators as $pageableGenerator) { - $key = $pageableGenerator::getKey(); - break; - } - - \assert($key !== null); - $pageableGenerator = $pageableGenerators[$key]; - } else { - $pageableGenerator = $pageableGenerators[$key] ?? null; - } - - if ($pageableGenerator === null) { - throw $this->createNotFoundException(); - } + $pageableGenerator = $this->pageableGenerators[$key] ?? throw $this->createNotFoundException(); $pageable = $pageableGenerator->generatePageable( itemsPerPage: $pagerParameters->itemsPerPage, @@ -93,11 +96,21 @@ public function index( $title = $pageableGenerator->getTitle(); - return $this->render('app/index.html.twig', [ + $pageIdentifier = $pager->getCurrentPage()->getPageIdentifier(); + $cloner = new VarCloner(); + $dumper = new HtmlDumper(); + + $dumper->setTheme('light'); + $output = fopen('php://memory', 'r+b') ?: throw new \RuntimeException('Failed to open memory stream'); + $dumper->dump($cloner->cloneVar($pageIdentifier), $output); + $output = stream_get_contents($output, -1, 0); + + return $this->render('app/page.html.twig', [ 'title' => $title, 'pager' => $pager, 'sql' => $logger, - 'pageable_generators' => $pageableGenerators, + 'page_identifier' => $output, + 'pageable_generators' => $this->pageableGenerators, 'source_code' => $this->getSourceCode($pageableGenerator::class), 'form' => $form->createView(), 'template' => $pagerParameters->template, @@ -106,6 +119,61 @@ public function index( ]); } + #[Route('/batch/{key?}', name: 'batch')] + public function batch( + SqlLogger $logger, + EntityManagerInterface $entityManager, + ?string $key, + ): Response { + $pageableGenerator = $this->pageableGenerators[$key] ?? throw $this->createNotFoundException(); + + /** @var PageableInterface */ + $pageable = $pageableGenerator->generatePageable( + itemsPerPage: 5, + count: false, + setName: 'medium', + ); + + // @highlight-start + + $output = '
    '; + + foreach ($pageable->withItemsPerPage(5)->getPages() as $page) { + $output .= '
  • '; + $output .= sprintf('Processing page %d', $page->getPageNumber() ?? 'null'); + + $output .= '
      '; + + foreach ($page as $item) { + $output .= sprintf( + '
    • Processing item id %s, date %s, title %s
    • ', + $item->getId(), + $item->getDate()?->format('Y-m-d') ?? 'null', + $item->getTitle() ?? 'null' + ); + } + + $output .= '
    '; + $output .= '
  • '; + + $entityManager->clear(); + } + + $output .= '
'; + // @highlight-end + + $title = $pageableGenerator->getTitle(); + + return $this->render('app/batch.html.twig', [ + 'title' => $title, + 'sql' => $logger, + 'pageable_generators' => $this->pageableGenerators, + 'source_code' => $this->getSourceCode($pageableGenerator::class) . "\n" . + $this->getSourceCode(self::class), + 'output' => $output, + ]); + } + /** * @param class-string $class */ diff --git a/tests/templates/app/base.html.twig b/tests/templates/app/base.html.twig new file mode 100644 index 0000000..030f7e1 --- /dev/null +++ b/tests/templates/app/base.html.twig @@ -0,0 +1,159 @@ + + + + + + {% block title %} + Rekapager + {% endblock title %} + — Rekapager Demo + + + + + + {{ importmap("app") }} + + + + + + +
+ {% block content %} + {% endblock %} +
+ + diff --git a/tests/templates/app/batch.html.twig b/tests/templates/app/batch.html.twig new file mode 100644 index 0000000..13facd8 --- /dev/null +++ b/tests/templates/app/batch.html.twig @@ -0,0 +1,23 @@ +{% extends "app/base.html.twig" %} + +{% block title %}Batch Demo: {{ title }}{% endblock title %} + +{% block content %} +

Batch Demo: {{ title }}

+ +

Source Code

+ +
{{- source_code -}}
+ +

SQL Queries

+ + {% for query in sql.logs %} + {% set runnable_sql = (query.sql ~ ';')|doctrine_replace_query_parameters(query.params) %} +

+        {{ runnable_sql }}
+ {% endfor %} + +

Output

+ + {{ output|raw }} +{% endblock content %} diff --git a/tests/templates/app/index.html.twig b/tests/templates/app/index.html.twig index aa9abc9..1743efc 100644 --- a/tests/templates/app/index.html.twig +++ b/tests/templates/app/index.html.twig @@ -1,190 +1,6 @@ - +{% extends "app/base.html.twig" %} +{% block content %} - - - {{ title }} - — Rekapager Demo - - - - - - {{ importmap('app') }} - - - - - - - -
-

{{ title }}

- -
-
-
- {{ rekapager(pager, template=template, locale=locale, proximity=proximity) }} -
-
-
- - -

Pager Parameters

- - {{ form_start(form) }} - {{ form_rest(form) }} - - {{ form_end(form) }} - -

SQL Queries

- - {% for query in sql.logs %} - {% set runnable_sql = (query.sql ~ ';')|doctrine_replace_query_parameters(query.params) %} - {# {{ runnable_sql|doctrine_format_sql(true) }} #} - {{ runnable_sql|doctrine_prettify_sql }} - {% endfor %} - -

Current Page Identifier

- - {{ dump(pager.currentPage.pageIdentifier) }} - -

Source Code

- -

-            {{- source_code|format_file_from_text -}}
-            
- -

Results

- -
- To test infinite scrolling, make your viewport narrower, and reload the page. -
- - - - - - - - - - - - - - {% for key,post in pager.currentPage %} - - - - - - - - {% endfor %} - -
Result KeyIDTitleDateContent
{{ key }}{{ post.id }}{{ post.title }}{{ post.date|date('Y-m-d') }}{{ post.content }}
-
- - + Use the menu above to navigate to a page. +{% endblock content %} diff --git a/tests/templates/app/page.html.twig b/tests/templates/app/page.html.twig new file mode 100644 index 0000000..6ae36d4 --- /dev/null +++ b/tests/templates/app/page.html.twig @@ -0,0 +1,67 @@ +{% extends "app/base.html.twig" %} + +{% block title %}Pagination Demo: {{ title }}{% endblock title %} + +{% block content %} +

Pagination Demo: {{ title }}

+ +
+
+
+ {{ rekapager(pager, template=template, locale=locale, proximity=proximity) }} +
+
+
+ +

Pager Parameters

+ + {{ form_start(form) }} + {{ form_rest(form) }} + + {{ form_end(form) }} + +

SQL Queries

+ + {% for query in sql.logs %} + {% set runnable_sql = (query.sql ~ ';')|doctrine_replace_query_parameters(query.params) %} +
{{- runnable_sql -}}
+ {% endfor %} + +

Current Page Identifier

+ + {{ page_identifier|raw }} + +

Source Code

+ +
{{- source_code -}}
+ +

Results

+ +
+ To test infinite scrolling, make your viewport narrower, and reload the page. +
+ + + + + + + + + + + + + + {% for key,post in pager.currentPage %} + + + + + + + + {% endfor %} + +
Result KeyIDTitleDateContent
{{ key }}{{ post.id }}{{ post.title }}{{ post.date|date("Y-m-d") }}{{ post.content }}
+{% endblock content %}