Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch pikchrshow-wasm Excluding Merge-Ins
This is equivalent to a diff from e6554457 to 98dd2626
2022-06-08
| ||
23:27 | Merge in pikchrshow-wasm branch: reimplement /pikchrshow using a client-side WASM build of pikchr.c, plus related feature-adjacent tweaks in mimetype handling. ... (check-in: 7fcb4626 user: stephan tags: trunk) | |
12:19 | Refactored the configure script's emsdk detection in the hopes that this reformulation will work on systems where emcc is installed via a package manager. ... (Closed-Leaf check-in: 98dd2626 user: stephan tags: pikchrshow-wasm) | |
10:09 | build.wiki: added notes about emsdk requirements and keeping it up to date. ... (check-in: b256c5bc user: stephan tags: pikchrshow-wasm) | |
2022-06-07
| ||
06:22 | Corrected a bit of misinformation in search.c's comments, per a report in the forum. ... (check-in: 6d6880c8 user: stephan tags: trunk) | |
2022-06-06
| ||
18:02 | Replace /pikchrshow with a WASM-based version and rename the prior version to /pikchrshowcs (cs=client/server). There are still a couple layout/style quirks to resolve, and a feature or two to port from the legacy app, but it more or less works. ... (check-in: 321f01a8 user: stephan tags: pikchrshow-wasm) | |
15:51 | Remove the '; charset=utf-8' suffix from response Content-Type headers. That modifier is technically incorrect for many mimetypes, but wasm loaders are extra picky about it and refuse to load wasm files with the charset set. An attempt at porting over althttpd's solution for this same problem leads down a much deeper and far more invasive rabbit hole because how fossil handles/sets the response content type is more involved than in althttpd. ... (Closed-Leaf check-in: 15e7b49e user: stephan tags: content-type-no-charset) | |
00:41 | Fix the new linear bisect so that it always goes from good to bad and stops at the first bad check-in found. ... (check-in: e6554457 user: drh tags: trunk) | |
2022-06-05
| ||
19:43 | Add the "fossil bisect option linear on" command that allows the "fossil bisect run" command to invoke a test script on every check-in along a path between two boundary check-ins. The "linear" option resets automatically opon "fossil bisect reset". ... (check-in: 42f61b67 user: drh tags: trunk) | |
Changes to Makefile.in.
︙ | ︙ | |||
70 71 72 73 74 75 76 77 78 79 80 81 82 83 | USE_LINENOISE = @USE_LINENOISE@ USE_MMAN_H = @USE_MMAN_H@ USE_SEE = @USE_SEE@ APPNAME = fossil # # APPNAME = fossil-fuzz # may be more appropriate for fuzzing. .PHONY: all tags include $(SRCDIR)/main.mk distclean: clean -rm -f autoconfig.h config.log Makefile | > > > > > > > > > > > | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | USE_LINENOISE = @USE_LINENOISE@ USE_MMAN_H = @USE_MMAN_H@ USE_SEE = @USE_SEE@ APPNAME = fossil # # APPNAME = fossil-fuzz # may be more appropriate for fuzzing. #### Emscripten stuff for optionally doing in-tree builds # of any WASM components. We store precompiled WASM in the the SCM, so # this is only useful for people who actively work on WASM # components. EMSDK_ENV refers to the "environment" script which comes # with the Emscripten SDK package: # https://emscripten.org/docs/getting_started/downloads.html EMSDK_HOME = @EMSDK_HOME@ EMSDK_ENV = @EMSDK_ENV@ EMCC_OPT = @EMCC_OPT@ EMCC_WRAPPER = $(SRCDIR_tools)/emcc.sh .PHONY: all tags include $(SRCDIR)/main.mk distclean: clean -rm -f autoconfig.h config.log Makefile |
︙ | ︙ |
Changes to auto.def.
︙ | ︙ | |||
26 27 28 29 30 31 32 33 34 35 36 37 38 39 | => {print the minimum SQLite version number required, and exit} internal-sqlite=1 => {Don't use the internal SQLite, use the system one} static=0 => {Link a static executable} fusefs=1 => {Disable the Fuse Filesystem} fossil-debug=0 => {Build with fossil debugging enabled} no-opt=0 => {Build without optimization} json=0 => {Build with fossil JSON API enabled} } # Update the minimum required SQLite version number here, and also # in src/main.c near the sqlite3_libversion_number() call. Take care # that both places agree! define MINIMUM_SQLITE_VERSION "3.38.0" | > | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | => {print the minimum SQLite version number required, and exit} internal-sqlite=1 => {Don't use the internal SQLite, use the system one} static=0 => {Link a static executable} fusefs=1 => {Disable the Fuse Filesystem} fossil-debug=0 => {Build with fossil debugging enabled} no-opt=0 => {Build without optimization} json=0 => {Build with fossil JSON API enabled} with-emsdk:path => {Directory containing the Emscripten SDK} } # Update the minimum required SQLite version number here, and also # in src/main.c near the sqlite3_libversion_number() call. Take care # that both places agree! define MINIMUM_SQLITE_VERSION "3.38.0" |
︙ | ︙ | |||
630 631 632 633 634 635 636 637 638 639 640 641 642 643 | # be checked for near the bottom of this file. # set tclconfig(TCL_LD_FLAGS) [string map [list -ldl ""] \ $tclconfig(TCL_LD_FLAGS)] define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS) define FOSSIL_ENABLE_TCL } # Network functions require libraries on some systems cc-check-function-in-lib gethostbyname nsl if {![cc-check-function-in-lib socket {socket network}]} { # Last resort, may be Windows if {[is_mingw]} { define-append LIBS -lwsock32 | > > > > > > > > > > > > > > > > > > > > > > > > > > > | 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 | # be checked for near the bottom of this file. # set tclconfig(TCL_LD_FLAGS) [string map [list -ldl ""] \ $tclconfig(TCL_LD_FLAGS)] define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS) define FOSSIL_ENABLE_TCL } # Emscripten is a purely optional component used only for doing # in-tree builds of WASM stuff, as opposed to WASM binaries we import # from other places. This is only set up for Unix-style OSes and is # untested anywhere but Linux. set emsdkHome [opt-val with-emsdk] define EMSDK_HOME "" define EMSDK_ENV "" define EMCC_OPT "-Oz" if {$emsdkHome eq "" && [info exists ::env(EMSDK)]} { # Fall back to checking the environment. $EMSDK gets set # by sourcing emsdk_env.sh. set emsdkHome $::env(EMSDK) } if {$emsdkHome ne ""} { define EMSDK_HOME $emsdkHome set emsdkEnv "$emsdkHome/emsdk_env.sh" if {[file exists $emsdkEnv]} { puts "Using Emscripten SDK environment from $emsdkEnv." define EMSDK_ENV $emsdkEnv if {[info exists ::env(EMCC_OPT)]} { define EMCC_OPT $::env(EMCC_OPT) } } else { puts "emsdk_env.sh not found. Assuming emcc is in the PATH." } } # Network functions require libraries on some systems cc-check-function-in-lib gethostbyname nsl if {![cc-check-function-in-lib socket {socket network}]} { # Last resort, may be Windows if {[is_mingw]} { define-append LIBS -lwsock32 |
︙ | ︙ | |||
724 725 726 727 728 729 730 731 732 733 | } if {[opt-bool static]} { # Linux can only infer the dependency on pthread from OpenSSL when # doing dynamic linkage. define-append LIBS -lpthread } make-template Makefile.in make-config-header autoconfig.h -auto {USE_* FOSSIL_*} | > > > > > > > > | 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 | } if {[opt-bool static]} { # Linux can only infer the dependency on pthread from OpenSSL when # doing dynamic linkage. define-append LIBS -lpthread } if {[get-define EMSDK_HOME] ne ""} { define EMCC_WRAPPER $::autosetup(dir)/../tools/emcc.sh make-template tools/emcc.sh.in catch {exec chmod u+x tools/emcc.sh} } else { define EMCC_WRAPPER "" catch {exec rm -f tools/emcc.sh} } make-template Makefile.in make-config-header autoconfig.h -auto {USE_* FOSSIL_*} |
Added extsrc/pikchr-worker.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | /* 2022-05-20 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: * May you do good and not evil. * May you find forgiveness for yourself and forgive others. * May you share freely, never taking more than you give. *********************************************************************** This is a JS Worker file for use with the pikchr wasm build. It loads the pikchr wasm module and offers access to it via the Worker message-passing interface. Because we can have only a single message handler, as opposed to an arbitrary number of discrete event listeners like with DOM elements, we have to define a lower-level message API. Messages abstractly look like: { type: string, data: type-specific value } Where 'type' is used for dispatching and 'data' is a 'type'-dependent value. The 'type' values expected by each side of the main/worker connection vary. The types are described below but subject to change at any time as this experiment evolves. Main-to-Worker message types: - pikchr: data=pikchr-format text to render or an object: { pikchr: source code for the pikchr, darkMode: boolean true to adjust colors for a dark color scheme, cssClass: CSS class name to add to the SVG } Workers-to-Main types - stdout, stderr: indicate stdout/stderr output from the wasm layer. The data property is the string of the output, noting that the emscripten binding emits these one line at a time. Thus, if a C-side puts() emits multiple lines in a single call, the JS side will see that as multiple calls. Example: {type:'stdout', data: 'Hi, world.'} - module: Status text. This is intended to alert the main thread about module loading status so that, e.g., the main thread can update a progress widget and DTRT when the module is finished loading and available for work. Status messages come in the form {type:'module', data:{ type:'status', data: {text:string|null, step:1-based-integer} } with an incrementing step value for each subsequent message. When the module loading is complete, a message with a text value of null is posted. - pikchr: {type: 'pikchr', data:{ pikchr: input text, result: rendered result (SVG on success, HTML on error), isError: bool, true if .result holds an error report, flags: integer: flags used to configure the pikchr rendering, width: if !isError, width (integer pixels) of the SVG, height: if !isError, height (integer pixels) of the SVG } } */ "use strict"; (function(){ /** Posts a message in the form {type,data} unless passed more than 2 args, in which case it posts {type, data:[arg1...argN]}. */ const wMsg = function(type,data){ postMessage({ type, data: arguments.length<3 ? data : Array.prototype.slice.call(arguments,1) }); }; const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));}; self.onerror = function(/*message, source, lineno, colno, error*/) { const err = arguments[4]; if(err && 'ExitStatus'==err.name){ /* This "cannot happen" for this wasm binding, but just in case... */ pikchrModule.isDead = true; stderr("FATAL ERROR:", err.message); stderr("Restarting the app requires reloading the page."); wMsg('error', err); } pikchrModule.setStatus('Exception thrown, see JavaScript console: '+err); }; self.onmessage = function f(ev){ ev = ev.data; switch(ev.type){ /** Runs the given text through pikchr and emits a 'pikchr' message result (output format documented above). Fires a working/start event before it starts and working/end event when it finishes. */ case 'pikchr': if(pikchrModule.isDead){ stderr("wasm module has exit()ed. Cannot pikchr."); return; } if(!f._){ f._ = pikchrModule.cwrap('pikchr', 'string', [ 'string'/*script*/, 'string'/*CSS class*/, 'number'/*flags*/, 'number'/*output: SVG width*/, 'number'/*output: SVG height*/ ]); } wMsg('working','start'); const stack = pikchrModule.stackSave(); try { const pnWidth = pikchrModule.stackAlloc(4), pnHeight = pikchrModule.stackAlloc(4); let script = '', flags = 0, cssClass = null; if('string'===typeof ev.data){ script = ev.data; }else if(ev.data && 'object'===typeof ev.data){ script = ev.data.pikchr; flags = ev.data.darkMode ? 0x02 : 0; if(ev.data.cssClass) cssClass = ev.data.cssClass; } pikchrModule.setValue(pnWidth, 0, "i32"); pikchrModule.setValue(pnHeight, 0, "i32"); const msg = { pikchr: script, result: (f._(script, cssClass, flags, pnWidth, pnHeight) || "").trim(), flags: flags }; msg.isError = !!(msg.result && msg.result.startsWith('<div')); if(msg.isError){ msg.width = msg.height = null; }else{ msg.width = pikchrModule.getValue(pnWidth, "i32"); msg.height = pikchrModule.getValue(pnHeight, "i32"); } wMsg('pikchr', msg); } finally { pikchrModule.stackRestore(stack); wMsg('working','end'); } return; }; console.warn("Unknown pikchr-worker message type:",ev); }; /** emscripten module for use with build mode -sMODULARIZE. */ const pikchrModule = { print: function(){wMsg('stdout', Array.prototype.slice.call(arguments));}, printErr: stderr, /** Intercepts status updates from the emscripting module init and fires worker events with a type of 'status' and a payload of: { text: string | null, // null at end of load process step: integer // starts at 1, increments 1 per call } We have no way of knowing in advance how many steps will be processed/posted, so creating a "percentage done" view is not really practical. One can be approximated by giving it a current value of message.step and max value of message.step+1, though. When work is finished, a message with a text value of null is submitted. After a message with text==null is posted, the module may later post messages about fatal problems, e.g. an exit() being triggered, so it is recommended that UI elements for posting status messages not be outright removed from the DOM when text==null, and that they instead be hidden until/unless text!=null. */ setStatus: function f(text){ if(!f.last) f.last = { step: 0, text: '' }; else if(text === f.last.text) return; f.last.text = text; wMsg('module',{ type:'status', data:{step: ++f.last.step, text: text||null} }); } }; importScripts('pikchr.js'); /** initPikchrModule() is installed via pikchr.js due to building with: emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initPikchrModule */ initPikchrModule(pikchrModule).then(function(thisModule){ wMsg('pikchrshow-ready'); }); })(); |
Added extsrc/pikchr.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 | var initPikchrModule = (() => { var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; return ( function(initPikchrModule) { initPikchrModule = initPikchrModule || {}; var Module = typeof initPikchrModule != "undefined" ? initPikchrModule : {}; var readyPromiseResolve, readyPromiseReject; Module["ready"] = new Promise(function(resolve, reject) { readyPromiseResolve = resolve; readyPromiseReject = reject; }); var moduleOverrides = Object.assign({}, Module); var arguments_ = []; var thisProgram = "./this.program"; var quit_ = (status, toThrow) => { throw toThrow; }; var ENVIRONMENT_IS_WEB = true; var ENVIRONMENT_IS_WORKER = false; var scriptDirectory = ""; function locateFile(path) { if (Module["locateFile"]) { return Module["locateFile"](path, scriptDirectory); } return scriptDirectory + path; } var read_, readAsync, readBinary, setWindowTitle; if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { if (ENVIRONMENT_IS_WORKER) { scriptDirectory = self.location.href; } else if (typeof document != "undefined" && document.currentScript) { scriptDirectory = document.currentScript.src; } if (_scriptDir) { scriptDirectory = _scriptDir; } if (scriptDirectory.indexOf("blob:") !== 0) { scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1); } else { scriptDirectory = ""; } { read_ = url => { var xhr = new XMLHttpRequest(); xhr.open("GET", url, false); xhr.send(null); return xhr.responseText; }; if (ENVIRONMENT_IS_WORKER) { readBinary = url => { var xhr = new XMLHttpRequest(); xhr.open("GET", url, false); xhr.responseType = "arraybuffer"; xhr.send(null); return new Uint8Array(xhr.response); }; } readAsync = (url, onload, onerror) => { var xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.responseType = "arraybuffer"; xhr.onload = () => { if (xhr.status == 200 || xhr.status == 0 && xhr.response) { onload(xhr.response); return; } onerror(); }; xhr.onerror = onerror; xhr.send(null); }; } setWindowTitle = title => document.title = title; } else {} var out = Module["print"] || console.log.bind(console); var err = Module["printErr"] || console.warn.bind(console); Object.assign(Module, moduleOverrides); moduleOverrides = null; if (Module["arguments"]) arguments_ = Module["arguments"]; if (Module["thisProgram"]) thisProgram = Module["thisProgram"]; if (Module["quit"]) quit_ = Module["quit"]; var wasmBinary; if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"]; var noExitRuntime = Module["noExitRuntime"] || true; if (typeof WebAssembly != "object") { abort("no native wasm support detected"); } var wasmMemory; var ABORT = false; var EXITSTATUS; function getCFunc(ident) { var func = Module["_" + ident]; return func; } function ccall(ident, returnType, argTypes, args, opts) { var toC = { "string": function(str) { var ret = 0; if (str !== null && str !== undefined && str !== 0) { var len = (str.length << 2) + 1; ret = stackAlloc(len); stringToUTF8(str, ret, len); } return ret; }, "array": function(arr) { var ret = stackAlloc(arr.length); writeArrayToMemory(arr, ret); return ret; } }; function convertReturnValue(ret) { if (returnType === "string") { return UTF8ToString(ret); } if (returnType === "boolean") return Boolean(ret); return ret; } var func = getCFunc(ident); var cArgs = []; var stack = 0; if (args) { for (var i = 0; i < args.length; i++) { var converter = toC[argTypes[i]]; if (converter) { if (stack === 0) stack = stackSave(); cArgs[i] = converter(args[i]); } else { cArgs[i] = args[i]; } } } var ret = func.apply(null, cArgs); function onDone(ret) { if (stack !== 0) stackRestore(stack); return convertReturnValue(ret); } ret = onDone(ret); return ret; } function cwrap(ident, returnType, argTypes, opts) { argTypes = argTypes || []; var numericArgs = argTypes.every(function(type) { return type === "number"; }); var numericRet = returnType !== "string"; if (numericRet && numericArgs && !opts) { return getCFunc(ident); } return function() { return ccall(ident, returnType, argTypes, arguments, opts); }; } var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder("utf8") : undefined; function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) { var endIdx = idx + maxBytesToRead; var endPtr = idx; while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); } else { var str = ""; while (idx < endPtr) { var u0 = heapOrArray[idx++]; if (!(u0 & 128)) { str += String.fromCharCode(u0); continue; } var u1 = heapOrArray[idx++] & 63; if ((u0 & 224) == 192) { str += String.fromCharCode((u0 & 31) << 6 | u1); continue; } var u2 = heapOrArray[idx++] & 63; if ((u0 & 240) == 224) { u0 = (u0 & 15) << 12 | u1 << 6 | u2; } else { u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | heapOrArray[idx++] & 63; } if (u0 < 65536) { str += String.fromCharCode(u0); } else { var ch = u0 - 65536; str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023); } } } return str; } function UTF8ToString(ptr, maxBytesToRead) { return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ""; } function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) { if (!(maxBytesToWrite > 0)) return 0; var startIdx = outIdx; var endIdx = outIdx + maxBytesToWrite - 1; for (var i = 0; i < str.length; ++i) { var u = str.charCodeAt(i); if (u >= 55296 && u <= 57343) { var u1 = str.charCodeAt(++i); u = 65536 + ((u & 1023) << 10) | u1 & 1023; } if (u <= 127) { if (outIdx >= endIdx) break; heap[outIdx++] = u; } else if (u <= 2047) { if (outIdx + 1 >= endIdx) break; heap[outIdx++] = 192 | u >> 6; heap[outIdx++] = 128 | u & 63; } else if (u <= 65535) { if (outIdx + 2 >= endIdx) break; heap[outIdx++] = 224 | u >> 12; heap[outIdx++] = 128 | u >> 6 & 63; heap[outIdx++] = 128 | u & 63; } else { if (outIdx + 3 >= endIdx) break; heap[outIdx++] = 240 | u >> 18; heap[outIdx++] = 128 | u >> 12 & 63; heap[outIdx++] = 128 | u >> 6 & 63; heap[outIdx++] = 128 | u & 63; } } heap[outIdx] = 0; return outIdx - startIdx; } function stringToUTF8(str, outPtr, maxBytesToWrite) { return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); } function writeArrayToMemory(array, buffer) { HEAP8.set(array, buffer); } var buffer, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64; function updateGlobalBufferAndViews(buf) { buffer = buf; Module["HEAP8"] = HEAP8 = new Int8Array(buf); Module["HEAP16"] = HEAP16 = new Int16Array(buf); Module["HEAP32"] = HEAP32 = new Int32Array(buf); Module["HEAPU8"] = HEAPU8 = new Uint8Array(buf); Module["HEAPU16"] = HEAPU16 = new Uint16Array(buf); Module["HEAPU32"] = HEAPU32 = new Uint32Array(buf); Module["HEAPF32"] = HEAPF32 = new Float32Array(buf); Module["HEAPF64"] = HEAPF64 = new Float64Array(buf); } var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 16777216; var wasmTable; var __ATPRERUN__ = []; var __ATINIT__ = []; var __ATPOSTRUN__ = []; var runtimeInitialized = false; function keepRuntimeAlive() { return noExitRuntime; } function preRun() { if (Module["preRun"]) { if (typeof Module["preRun"] == "function") Module["preRun"] = [ Module["preRun"] ]; while (Module["preRun"].length) { addOnPreRun(Module["preRun"].shift()); } } callRuntimeCallbacks(__ATPRERUN__); } function initRuntime() { runtimeInitialized = true; callRuntimeCallbacks(__ATINIT__); } function postRun() { if (Module["postRun"]) { if (typeof Module["postRun"] == "function") Module["postRun"] = [ Module["postRun"] ]; while (Module["postRun"].length) { addOnPostRun(Module["postRun"].shift()); } } callRuntimeCallbacks(__ATPOSTRUN__); } function addOnPreRun(cb) { __ATPRERUN__.unshift(cb); } function addOnInit(cb) { __ATINIT__.unshift(cb); } function addOnPostRun(cb) { __ATPOSTRUN__.unshift(cb); } var runDependencies = 0; var runDependencyWatcher = null; var dependenciesFulfilled = null; function addRunDependency(id) { runDependencies++; if (Module["monitorRunDependencies"]) { Module["monitorRunDependencies"](runDependencies); } } function removeRunDependency(id) { runDependencies--; if (Module["monitorRunDependencies"]) { Module["monitorRunDependencies"](runDependencies); } if (runDependencies == 0) { if (runDependencyWatcher !== null) { clearInterval(runDependencyWatcher); runDependencyWatcher = null; } if (dependenciesFulfilled) { var callback = dependenciesFulfilled; dependenciesFulfilled = null; callback(); } } } function abort(what) { { if (Module["onAbort"]) { Module["onAbort"](what); } } what = "Aborted(" + what + ")"; err(what); ABORT = true; EXITSTATUS = 1; what += ". Build with -sASSERTIONS for more info."; var e = new WebAssembly.RuntimeError(what); readyPromiseReject(e); throw e; } var dataURIPrefix = "data:application/octet-stream;base64,"; function isDataURI(filename) { return filename.startsWith(dataURIPrefix); } var wasmBinaryFile; wasmBinaryFile = "pikchr.wasm"; if (!isDataURI(wasmBinaryFile)) { wasmBinaryFile = locateFile(wasmBinaryFile); } function getBinary(file) { try { if (file == wasmBinaryFile && wasmBinary) { return new Uint8Array(wasmBinary); } if (readBinary) { return readBinary(file); } else { throw "both async and sync fetching of the wasm failed"; } } catch (err) { abort(err); } } function getBinaryPromise() { if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) { if (typeof fetch == "function") { return fetch(wasmBinaryFile, { credentials: "same-origin" }).then(function(response) { if (!response["ok"]) { throw "failed to load wasm binary file at '" + wasmBinaryFile + "'"; } return response["arrayBuffer"](); }).catch(function() { return getBinary(wasmBinaryFile); }); } } return Promise.resolve().then(function() { return getBinary(wasmBinaryFile); }); } function createWasm() { var info = { "a": asmLibraryArg }; function receiveInstance(instance, module) { var exports = instance.exports; Module["asm"] = exports; wasmMemory = Module["asm"]["d"]; updateGlobalBufferAndViews(wasmMemory.buffer); wasmTable = Module["asm"]["g"]; addOnInit(Module["asm"]["e"]); removeRunDependency("wasm-instantiate"); } addRunDependency("wasm-instantiate"); function receiveInstantiationResult(result) { receiveInstance(result["instance"]); } function instantiateArrayBuffer(receiver) { return getBinaryPromise().then(function(binary) { return WebAssembly.instantiate(binary, info); }).then(function(instance) { return instance; }).then(receiver, function(reason) { err("failed to asynchronously prepare wasm: " + reason); abort(reason); }); } function instantiateAsync() { if (!wasmBinary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(wasmBinaryFile) && typeof fetch == "function") { return fetch(wasmBinaryFile, { credentials: "same-origin" }).then(function(response) { var result = WebAssembly.instantiateStreaming(response, info); return result.then(receiveInstantiationResult, function(reason) { err("wasm streaming compile failed: " + reason); err("falling back to ArrayBuffer instantiation"); return instantiateArrayBuffer(receiveInstantiationResult); }); }); } else { return instantiateArrayBuffer(receiveInstantiationResult); } } if (Module["instantiateWasm"]) { try { var exports = Module["instantiateWasm"](info, receiveInstance); return exports; } catch (e) { err("Module.instantiateWasm callback failed with error: " + e); return false; } } instantiateAsync().catch(readyPromiseReject); return {}; } var tempDouble; var tempI64; function callRuntimeCallbacks(callbacks) { while (callbacks.length > 0) { var callback = callbacks.shift(); if (typeof callback == "function") { callback(Module); continue; } var func = callback.func; if (typeof func == "number") { if (callback.arg === undefined) { getWasmTableEntry(func)(); } else { getWasmTableEntry(func)(callback.arg); } } else { func(callback.arg === undefined ? null : callback.arg); } } } function getValue(ptr, type = "i8") { if (type.endsWith("*")) type = "i32"; switch (type) { case "i1": return HEAP8[ptr >> 0]; case "i8": return HEAP8[ptr >> 0]; case "i16": return HEAP16[ptr >> 1]; case "i32": return HEAP32[ptr >> 2]; case "i64": return HEAP32[ptr >> 2]; case "float": return HEAPF32[ptr >> 2]; case "double": return Number(HEAPF64[ptr >> 3]); default: abort("invalid type for getValue: " + type); } return null; } function getWasmTableEntry(funcPtr) { return wasmTable.get(funcPtr); } function setValue(ptr, value, type = "i8") { if (type.endsWith("*")) type = "i32"; switch (type) { case "i1": HEAP8[ptr >> 0] = value; break; case "i8": HEAP8[ptr >> 0] = value; break; case "i16": HEAP16[ptr >> 1] = value; break; case "i32": HEAP32[ptr >> 2] = value; break; case "i64": tempI64 = [ value >>> 0, (tempDouble = value, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0) ], HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1]; break; case "float": HEAPF32[ptr >> 2] = value; break; case "double": HEAPF64[ptr >> 3] = value; break; default: abort("invalid type for setValue: " + type); } } function ___assert_fail(condition, filename, line, func) { abort("Assertion failed: " + UTF8ToString(condition) + ", at: " + [ filename ? UTF8ToString(filename) : "unknown filename", line, func ? UTF8ToString(func) : "unknown function" ]); } function abortOnCannotGrowMemory(requestedSize) { abort("OOM"); } function _emscripten_resize_heap(requestedSize) { var oldSize = HEAPU8.length; requestedSize = requestedSize >>> 0; abortOnCannotGrowMemory(requestedSize); } function _exit(status) { exit(status); } var asmLibraryArg = { "a": ___assert_fail, "b": _emscripten_resize_heap, "c": _exit }; var asm = createWasm(); var ___wasm_call_ctors = Module["___wasm_call_ctors"] = function() { return (___wasm_call_ctors = Module["___wasm_call_ctors"] = Module["asm"]["e"]).apply(null, arguments); }; var _pikchr = Module["_pikchr"] = function() { return (_pikchr = Module["_pikchr"] = Module["asm"]["f"]).apply(null, arguments); }; var stackSave = Module["stackSave"] = function() { return (stackSave = Module["stackSave"] = Module["asm"]["h"]).apply(null, arguments); }; var stackRestore = Module["stackRestore"] = function() { return (stackRestore = Module["stackRestore"] = Module["asm"]["i"]).apply(null, arguments); }; var stackAlloc = Module["stackAlloc"] = function() { return (stackAlloc = Module["stackAlloc"] = Module["asm"]["j"]).apply(null, arguments); }; Module["cwrap"] = cwrap; Module["stackSave"] = stackSave; Module["stackRestore"] = stackRestore; Module["setValue"] = setValue; Module["getValue"] = getValue; var calledRun; function ExitStatus(status) { this.name = "ExitStatus"; this.message = "Program terminated with exit(" + status + ")"; this.status = status; } dependenciesFulfilled = function runCaller() { if (!calledRun) run(); if (!calledRun) dependenciesFulfilled = runCaller; }; function run(args) { args = args || arguments_; if (runDependencies > 0) { return; } preRun(); if (runDependencies > 0) { return; } function doRun() { if (calledRun) return; calledRun = true; Module["calledRun"] = true; if (ABORT) return; initRuntime(); readyPromiseResolve(Module); if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"](); postRun(); } if (Module["setStatus"]) { Module["setStatus"]("Running..."); setTimeout(function() { setTimeout(function() { Module["setStatus"](""); }, 1); doRun(); }, 1); } else { doRun(); } } Module["run"] = run; function exit(status, implicit) { EXITSTATUS = status; procExit(status); } function procExit(code) { EXITSTATUS = code; if (!keepRuntimeAlive()) { if (Module["onExit"]) Module["onExit"](code); ABORT = true; } quit_(code, new ExitStatus(code)); } if (Module["preInit"]) { if (typeof Module["preInit"] == "function") Module["preInit"] = [ Module["preInit"] ]; while (Module["preInit"].length > 0) { Module["preInit"].pop()(); } } run(); return initPikchrModule.ready } ); })(); if (typeof exports === 'object' && typeof module === 'object') module.exports = initPikchrModule; else if (typeof define === 'function' && define['amd']) define([], function() { return initPikchrModule; }); else if (typeof exports === 'object') exports["initPikchrModule"] = initPikchrModule; |
Added extsrc/pikchr.wasm.
cannot compute difference between binary files
Changes to skins/darkmode/css.txt.
︙ | ︙ | |||
129 130 131 132 133 134 135 | outline: 0 } .button:focus, button:focus, input[type=button]:focus, input[type=reset]:focus, input[type=submit]:focus { | | < | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | outline: 0 } .button:focus, button:focus, input[type=button]:focus, input[type=reset]:focus, input[type=submit]:focus { outline: 2px outset #333; border-color: #888; } /* All page content from the bottom of the menu or submenu down to ** the footer */ div.content { padding: 0ex 1ex 1ex 1ex; } |
︙ | ︙ |
Changes to src/builtin.c.
︙ | ︙ | |||
128 129 130 131 132 133 134 | blob_init(&x, (const char*)pData, nByte); blob_write_to_file(&x, g.argc==4 ? g.argv[3] : "-"); blob_reset(&x); } /* ** Input zList is a list of numeric identifiers for files in | | | < | | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | blob_init(&x, (const char*)pData, nByte); blob_write_to_file(&x, g.argc==4 ? g.argv[3] : "-"); blob_reset(&x); } /* ** Input zList is a list of numeric identifiers for files in ** aBuiltinFiles[]. Return the concatenation of all of those files ** using mimetype zType, or as text/javascript if zType is 0. */ static void builtin_deliver_multiple_js_files( const char *zList, /* List of numeric identifiers */ const char *zType /* Override mimetype */ ){ Blob *pOut; if( zType==0 ) zType = "text/javascript"; cgi_set_content_type(zType); pOut = cgi_output_blob(); while( zList[0] ){ int i = atoi(zList); if( i>0 && i<=count(aBuiltinFiles) ){ blob_appendf(pOut, "/* %s */\n", aBuiltinFiles[i-1].zName); blob_append(pOut, (const char*)aBuiltinFiles[i-1].pData, |
︙ | ︙ | |||
201 202 203 204 205 206 207 | } cgi_set_status(404, "Not Found"); @ File "%h(zName)" not found return; } if( zType==0 ){ if( sqlite3_strglob("*.js", zName)==0 ){ | | | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | } cgi_set_status(404, "Not Found"); @ File "%h(zName)" not found return; } if( zType==0 ){ if( sqlite3_strglob("*.js", zName)==0 ){ zType = "text/javascript"; }else{ zType = mimetype_from_name(zName); } } cgi_set_content_type(zType); if( zId && (nId = (int)strlen(zId))>=8 |
︙ | ︙ |
Changes to src/cgi.c.
︙ | ︙ | |||
305 306 307 308 309 310 311 | ){ char const *zSecure = ""; if(!g.isHTTP) return /* e.g. JSON CLI mode, where g.zTop is not set */; else if( zPath==0 ){ zPath = g.zTop; if( zPath[0]==0 ) zPath = "/"; } | | | 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | ){ char const *zSecure = ""; if(!g.isHTTP) return /* e.g. JSON CLI mode, where g.zTop is not set */; else if( zPath==0 ){ zPath = g.zTop; if( zPath[0]==0 ) zPath = "/"; } if( g.zBaseURL!=0 && fossil_strncmp(g.zBaseURL, "https:", 6)==0 ){ zSecure = " secure;"; } if( lifetime!=0 ){ blob_appendf(&extraHeader, "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; " "%s Version=1\r\n", zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure); |
︙ | ︙ | |||
328 329 330 331 332 333 334 | /* ** Return true if the response should be sent with Content-Encoding: gzip. */ static int is_gzippable(void){ if( g.fNoHttpCompress ) return 0; if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; | > > > | > > | > > > > > > > > > | > > > > > > > > > > | 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 | /* ** Return true if the response should be sent with Content-Encoding: gzip. */ static int is_gzippable(void){ if( g.fNoHttpCompress ) return 0; if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; /* Maintenance note: this oddball structure is intended to make ** adding new mimetypes to this list less of a performance hit than ** doing a strcmp/glob over a growing set of compressible types. */ switch(zContentType ? *zContentType : 0){ case (int)'a': if(0==fossil_strncmp("application/",zContentType,12)){ const char * z = &zContentType[12]; switch(*z){ case (int)'j': return fossil_strcmp("javascript", z)==0 || fossil_strcmp("json", z)==0; case (int)'w': return fossil_strcmp("wasm", z)==0; case (int)'x': return fossil_strcmp("x-tcl", z)==0 || fossil_strcmp("x-tar", z)==0; default: return sqlite3_strglob("*xml", z)==0; } } break; case (int)'i': return fossil_strcmp(zContentType, "image/svg+xml")==0 || fossil_strcmp(zContentType, "image/vnd.microsoft.icon")==0; case (int)'t': return fossil_strncmp(zContentType, "text/", 5)==0; } return 0; } /* ** The following routines read or write content from/to the wire for ** an HTTP request. Depending on settings the content might be coming ** from or going to a socket, or a file, or it might come from or go |
︙ | ︙ | |||
417 418 419 420 421 422 423 424 425 426 427 428 429 430 | */ static void cgi_fflush(void){ if( !g.httpUseSSL ){ fflush(g.httpOut); } } /* ** Generate the reply to a web request. The output might be an ** full HTTP response, or a CGI response, depending on how things have ** be set up. ** ** The reply consists of a response header (an HTTP or CGI response header) | > > > > > > > > > > > > > > > > > > > | 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 | */ static void cgi_fflush(void){ if( !g.httpUseSSL ){ fflush(g.httpOut); } } /* ** Given a Content-Type value, returns a string suitable for appending ** to the Content-Type header for adding (or not) the "; charset=..." ** part. It returns an empty string for most types or if zContentType ** is NULL. ** ** See forum post f60dece061c364d1 for the discussions which lead to ** this. Previously we always appended the charset, but WASM loaders ** are pedantic and refuse to load any responses which have a ** charset. Also, adding a charset is not strictly appropriate for ** most types (and not required for many others which may ostensibly ** benefit from one, as detailed in that forum post). */ static const char * content_type_charset(const char *zContentType){ if(0==fossil_strncmp(zContentType,"text/",5)){ return "; charset=utf-8"; } return ""; } /* ** Generate the reply to a web request. The output might be an ** full HTTP response, or a CGI response, depending on how things have ** be set up. ** ** The reply consists of a response header (an HTTP or CGI response header) |
︙ | ︙ | |||
491 492 493 494 495 496 497 | ** a CGI script. */ /* Content intended for logged in users should only be cached in ** the browser, not some shared location. */ if( iReplyStatus!=304 ) { | | > | 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 | ** a CGI script. */ /* Content intended for logged in users should only be cached in ** the browser, not some shared location. */ if( iReplyStatus!=304 ) { blob_appendf(&hdr, "Content-Type: %s%s\r\n", zContentType, content_type_charset(zContentType)); if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){ cgi_combine_header_and_body(); blob_compress(&cgiContent[0], &cgiContent[0]); } if( is_gzippable() && iReplyStatus!=206 ){ int i; |
︙ | ︙ | |||
567 568 569 570 571 572 573 | NORETURN void cgi_redirect_with_status( const char *zURL, int iStat, const char *zStat ){ char *zLocation; CGIDEBUG(("redirect to %s\n", zURL)); | > | | 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 | NORETURN void cgi_redirect_with_status( const char *zURL, int iStat, const char *zStat ){ char *zLocation; CGIDEBUG(("redirect to %s\n", zURL)); if( fossil_strncmp(zURL,"http:",5)==0 || fossil_strncmp(zURL,"https:",6)==0 ){ zLocation = mprintf("Location: %s\r\n", zURL); }else if( *zURL=='/' ){ int n1 = (int)strlen(g.zBaseURL); int n2 = (int)strlen(g.zTop); if( g.zBaseURL[n1-1]=='/' ) zURL++; zLocation = mprintf("Location: %.*s%s\r\n", n1-n2, g.zBaseURL, zURL); }else{ |
︙ | ︙ | |||
651 652 653 654 655 656 657 | if( zRef==0 ) return 0; if( requirePost ){ const char *zMethod = P("REQUEST_METHOD"); if( zMethod==0 ) return 0; if( strcmp(zMethod,"POST")!=0 ) return 0; } nBase = (int)strlen(g.zBaseURL); | | | 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 | if( zRef==0 ) return 0; if( requirePost ){ const char *zMethod = P("REQUEST_METHOD"); if( zMethod==0 ) return 0; if( strcmp(zMethod,"POST")!=0 ) return 0; } nBase = (int)strlen(g.zBaseURL); if( fossil_strncmp(g.zBaseURL,zRef,nBase)!=0 ) return 0; if( zRef[nBase]!=0 && zRef[nBase]!='/' ) return 0; return 1; } /* ** Information about all query parameters, post parameter, cookies and ** CGI environment variables are stored in a hash table as follows: |
︙ | ︙ | |||
918 919 920 921 922 923 924 | ){ char *z = *pz; int len = *pLen; int i; int nBoundary = strlen(zBoundary); *pnContent = len; for(i=0; i<len; i++){ | | > | 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 | ){ char *z = *pz; int len = *pLen; int i; int nBoundary = strlen(zBoundary); *pnContent = len; for(i=0; i<len; i++){ if( z[i]=='\n' && fossil_strncmp(zBoundary, &z[i+1], nBoundary)==0 ){ if( i>0 && z[i-1]=='\r' ) i--; z[i] = 0; *pnContent = i; i += nBoundary; break; } } |
︙ | ︙ | |||
1346 1347 1348 1349 1350 1351 1352 | /* ** Decode POST parameter information in the cgiIn content, if any. */ void cgi_decode_post_parameters(void){ int len = blob_size(&g.cgiIn); if( len==0 ) return; if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0 | | | 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 | /* ** Decode POST parameter information in the cgiIn content, if any. */ void cgi_decode_post_parameters(void){ int len = blob_size(&g.cgiIn); if( len==0 ) return; if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0 || fossil_strncmp(g.zContentType,"multipart/form-data",19)==0 ){ char *z = blob_str(&g.cgiIn); cgi_trace(z); if( g.zContentType[0]=='a' ){ add_param_list(z, '&'); }else{ process_multipart_form_data(z, len); |
︙ | ︙ | |||
2324 2325 2326 2327 2328 2329 2330 | g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort); fflush(stdout); if( zBrowser ){ assert( strstr(zBrowser,"%d")!=0 ); zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); #if defined(__CYGWIN__) /* On Cygwin, we can do better than "echo" */ | | | 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 | g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort); fflush(stdout); if( zBrowser ){ assert( strstr(zBrowser,"%d")!=0 ); zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); #if defined(__CYGWIN__) /* On Cygwin, we can do better than "echo" */ if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){ wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5); wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */ if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){ fossil_warning("cannot start browser\n"); } }else #endif |
︙ | ︙ | |||
2484 2485 2486 2487 2488 2489 2490 | static const char *const azMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore, &mday, zMonth, &year, &hour, &min, &sec)){ if( year > 1900 ) year -= 1900; for(mon=0; azMonths[mon]; mon++){ | | | 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 | static const char *const azMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore, &mday, zMonth, &year, &hour, &min, &sec)){ if( year > 1900 ) year -= 1900; for(mon=0; azMonths[mon]; mon++){ if( !fossil_strncmp( azMonths[mon], zMonth, 3 )){ int nDay; int isLeapYr; static int priorDays[] = { 0, 31, 59, 90,120,151,181,212,243,273,304,334 }; if( mon<0 ){ int nYear = (11 - mon)/12; year -= nYear; |
︙ | ︙ |
Changes to src/default.css.
︙ | ︙ | |||
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 | } .capsumWrite { background-color: #ffb; } label { white-space: nowrap; } .copy-button { display: inline-block; width: 14px; height: 14px; /*Note: .24em is slightly smaller than the average width of a normal space.*/ margin: -2px .24em 0 0; padding: 0; | > > > | 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 | } .capsumWrite { background-color: #ffb; } label { white-space: nowrap; } label[for] { cursor: pointer; } .copy-button { display: inline-block; width: 14px; height: 14px; /*Note: .24em is slightly smaller than the average width of a normal space.*/ margin: -2px .24em 0 0; padding: 0; |
︙ | ︙ | |||
1073 1074 1075 1076 1077 1078 1079 | color: darkred; background: yellow; } .warning { color: black; background: yellow; } | | | | | | > > > > > | | 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 | color: darkred; background: yellow; } .warning { color: black; background: yellow; } .hidden, .initially-hidden { /* The framework-wide way of hiding elements is to assign them th .hidden class. To make them visible again, remove it. The !important qualifiers are unfortunate but sometimes necessary when hidden element has other classes which specify visibility-related options. The .initially-hidden class is for pages which need to show, e.g., a progress widget while a large WASM blob loads. Elements aside from that load-time widget can be made .initially-hidden and then have that class removed once the long-running startup process is done. See /pikchrshow for an example. */ position: absolute !important; opacity: 0 !important; pointer-events: none !important; display: none !important; } input { max-width: 95%; |
︙ | ︙ |
Changes to src/doc.c.
︙ | ︙ | |||
154 155 156 157 158 159 160 | { "ips", 3, "application/x-ipscript" }, { "ipx", 3, "application/x-ipix" }, { "jad", 3, "text/vnd.sun.j2me.app-descriptor" }, { "jar", 3, "application/java-archive" }, { "jpe", 3, "image/jpeg" }, { "jpeg", 4, "image/jpeg" }, { "jpg", 3, "image/jpeg" }, | | > > > > > > | 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | { "ips", 3, "application/x-ipscript" }, { "ipx", 3, "application/x-ipix" }, { "jad", 3, "text/vnd.sun.j2me.app-descriptor" }, { "jar", 3, "application/java-archive" }, { "jpe", 3, "image/jpeg" }, { "jpeg", 4, "image/jpeg" }, { "jpg", 3, "image/jpeg" }, { "js", 2, "text/javascript" }, /* application/javascript is commonly used for JS, but the ** spec says text/javascript is correct: ** https://html.spec.whatwg.org/multipage/scripting.html ** #scriptingLanguages:javascript-mime-type */ { "json", 4, "application/json" }, { "kar", 3, "audio/midi" }, { "latex", 5, "application/x-latex" }, { "lha", 3, "application/octet-stream" }, { "lsp", 3, "application/x-lisp" }, { "lzh", 3, "application/octet-stream" }, { "m", 1, "text/plain" }, { "m3u", 3, "audio/x-mpegurl" }, { "man", 3, "text/plain" }, { "markdown", 8, "text/x-markdown" }, { "md", 2, "text/x-markdown" }, { "me", 2, "application/x-troff-me" }, { "mesh", 4, "model/mesh" }, { "mid", 3, "audio/midi" }, { "midi", 4, "audio/midi" }, { "mif", 3, "application/x-mif" }, { "mime", 4, "www/mime" }, { "mjs", 3, "text/javascript" /*EM6 modules*/ }, { "mkd", 3, "text/x-markdown" }, { "mov", 3, "video/quicktime" }, { "movie", 5, "video/x-sgi-movie" }, { "mp2", 3, "audio/mpeg" }, { "mp3", 3, "audio/mpeg" }, { "mp4", 3, "video/mp4" }, { "mpe", 3, "video/mpeg" }, |
︙ | ︙ |
Changes to src/fossil.page.pikchrshow.js.
1 2 3 | (function(F/*the fossil object*/){ "use strict"; /** | | > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | (function(F/*the fossil object*/){ "use strict"; /** Client-side implementation of the /pikchrshowcs app. Requires that the fossil JS bootstrapping is complete and that these fossil JS APIs have been installed: fossil.fetch, fossil.dom, fossil.copybutton, fossil.popupwidget, fossil.storage Maintenance funkiness note: this file is for the legacy /pikchrshowcs app, which was formerly named /pikchrshow. This file and its replacement were not renamed because the replacement impl would end up getting this file's name and cause confusion in the file history. Whether that confusion would be less than this file's name matching the _other_ /pikchrshow impl will cause more or less confusion than that remains to be seen. */ const E = (s)=>document.querySelector(s), D = F.dom, P = F.page; P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown, 2==pikchr text fossil, 3==raw SVG. */ |
︙ | ︙ |
Added src/fossil.page.pikchrshowasm.js.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 | /* 2022-05-20 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: * May you do good and not evil. * May you find forgiveness for yourself and forgive others. * May you share freely, never taking more than you give. *********************************************************************** This is the main entry point for the WASM rendition of fossil's /pikchrshow app. It sets up the various UI bits, loads a Worker for the pikchr process, and manages the communication between the UI and worker. API dependencies: fossil.dom, fossil.copybutton, fossil.storage */ (function(F/*fossil object*/){ 'use strict'; /* Recall that the 'self' symbol, except where locally overwritten, refers to the global window or worker object. */ const D = F.dom; /** Name of the stored copy of this app's config. */ const configStorageKey = 'pikchrshow-config'; /* querySelectorAll() proxy */ const EAll = function(/*[element=document,] cssSelector*/){ return (arguments.length>1 ? arguments[0] : document) .querySelectorAll(arguments[arguments.length-1]); }; /* querySelector() proxy */ const E = function(/*[element=document,] cssSelector*/){ return (arguments.length>1 ? arguments[0] : document) .querySelector(arguments[arguments.length-1]); }; /** The main application object. */ const PS = { /* Config options. */ config: { /* If true, display input/output areas side-by-side, else stack them vertically. */ sideBySide: true, /* If true, swap positions of the input/output areas. */ swapInOut: false, /* If true, the SVG is allowed to resize to fit the parent content area, else the parent is resized to fit the rendered SVG (as sized by pikchr). */ renderAutofit: false, /* If true, automatically render while the user is typing. */ renderWhileTyping: false }, /* Various DOM elements. */ e: { previewCopyButton: E('#preview-copy-button'), previewModeLabel: E('label[for=preview-copy-button]'), zoneInputButtons: E('.zone-wrapper.input > legend > .button-bar'), zoneOutputButtons: E('.zone-wrapper.output > legend > .button-bar'), outText: E('#pikchr-output-text'), pikOutWrapper: E('#pikchr-output-wrapper'), pikOut: E('#pikchr-output'), btnRender: E('#btn-render') }, renderModes: ['svg'/*SVG must be at index 0*/,'markdown', 'wiki', 'text'], renderModeLabels: { svg: 'SVG', markdown: 'Markdown', wiki: 'Fossil Wiki', text: 'Text' }, _msgMap: {}, /** Adds a worker message handler for messages of the given type. */ addMsgHandler: function f(type,callback){ if(Array.isArray(type)){ type.forEach((t)=>this.addMsgHandler(t, callback)); return this; } (this._msgMap.hasOwnProperty(type) ? this._msgMap[type] : (this._msgMap[type] = [])).push(callback); return this; }, /** Given a worker message, runs all handlers for msg.type. */ runMsgHandlers: function(msg){ const list = (this._msgMap.hasOwnProperty(msg.type) ? this._msgMap[msg.type] : false); if(!list){ console.warn("No handlers found for message type:",msg); return false; } list.forEach((f)=>f(msg)); return true; }, /** Removes all message handlers for the given message type. */ clearMsgHandlers: function(type){ delete this._msgMap[type]; return this; }, /* Posts a message in the form {type, data} to the db worker. Returns this. */ wMsg: function(type,data){ this.worker.postMessage({type, data}); return this; }, /** Stores this object's config in the browser's storage. */ storeConfig: function(){ F.storage.setJSON(configStorageKey,this.config); } }; PS.renderModes.selectedIndex = 0; PS._config = F.storage.getJSON(configStorageKey); if(PS._config){ /* Copy all properties to PS.config which are currently in PS._config. We don't bother copying any other properties: those would be stale/removed config entries. */ Object.keys(PS.config).forEach(function(k){ if(PS._config.hasOwnProperty(k)){ PS.config[k] = PS._config[k]; } }); delete PS._config; } PS.worker = new Worker('builtin/extsrc/pikchr-worker.js'); PS.worker.onmessage = (ev)=>PS.runMsgHandlers(ev.data); PS.addMsgHandler('stdout', console.log.bind(console)); PS.addMsgHandler('stderr', console.error.bind(console)); /** Handles status updates from the Module object. */ PS.addMsgHandler('module', function f(ev){ ev = ev.data; if('status'!==ev.type){ console.warn("Unexpected module-type message:",ev); return; } if(!f.ui){ f.ui = { status: E('#module-status'), progress: E('#module-progress'), spinner: E('#module-spinner') }; } const msg = ev.data; if(f.ui.progres){ progress.value = msg.step; progress.max = msg.step + 1/*we don't know how many steps to expect*/; } if(1==msg.step){ f.ui.progress.classList.remove('hidden'); f.ui.spinner.classList.remove('hidden'); } if(msg.text){ f.ui.status.classList.remove('hidden'); f.ui.status.innerText = msg.text; }else{ if(f.ui.progress){ f.ui.progress.remove(); f.ui.spinner.remove(); delete f.ui.progress; delete f.ui.spinner; } f.ui.status.classList.add('hidden'); /* The module can post messages about fatal problems, e.g. an exit() being triggered or assertion failure, after the last "load" message has arrived, so leave f.ui.status and message listener intact. */ } }); PS.e.previewModeLabel.innerText = PS.renderModeLabels[PS.renderModes[PS.renderModes.selectedIndex]]; /** The 'pikchrshow-ready' event is fired (with no payload) when the wasm module has finished loading. */ PS.addMsgHandler('pikchrshow-ready', function(){ PS.clearMsgHandlers('pikchrshow-ready'); F.page.onPikchrshowLoaded(); }); /** Performs all app initialization which must wait until after the worker module is loaded. This function removes itself when it's called. */ F.page.onPikchrshowLoaded = function(){ delete this.onPikchrshowLoaded; // Unhide all elements which start out hidden EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden')); const taInput = E('#input'); const btnClearIn = E('#btn-clear'); btnClearIn.addEventListener('click',function(){ taInput.value = ''; },false); const getCurrentText = function(){ let text; if(taInput.selectionStart<taInput.selectionEnd){ text = taInput.value.substring(taInput.selectionStart,taInput.selectionEnd).trim(); }else{ text = taInput.value.trim(); } return text;; }; const renderCurrentText = function(){ const text = getCurrentText(); if(text) PS.render(text); }; const setCurrentText = function(txt){ taInput.value = txt; renderCurrentText(); }; PS.e.btnRender.addEventListener('click',function(ev){ ev.preventDefault(); renderCurrentText(); },false); /** To be called immediately before work is sent to the worker. Updates some UI elements. The 'working'/'end' event will apply the inverse, undoing the bits this function does. This impl is not in the 'working'/'start' event handler because that event is given to us asynchronously _after_ we need to have performed this work. */ const preStartWork = function f(){ if(!f._){ const title = E('title'); f._ = { pageTitle: title, pageTitleOrig: title.innerText }; } //f._.pageTitle.innerText = "[working...] "+f._.pageTitleOrig; PS.e.btnRender.setAttribute('disabled','disabled'); }; /** Submits the current input text to pikchr and renders the result. */ PS.render = function f(txt){ preStartWork(); this.wMsg('pikchr',{ pikchr: txt, darkMode: !!window.fossil.config.skin.isDark }); }; /** Event handler for 'pikchr' messages from the Worker thread. */ PS.addMsgHandler('pikchr', function(ev){ const m = ev.data, pikOut = this.e.pikOut; pikOut.classList[m.isError ? 'add' : 'remove']('error'); pikOut.dataset.pikchr = m.pikchr; const mode = this.renderModes[this.renderModes.selectedIndex]; switch(mode){ case 'text': case 'markdown': case 'wiki': { let body; switch(mode){ case 'markdown': body = ['```pikchr', m.pikchr, '```'].join('\n'); break; case 'wiki': body = ['<verbatim type="pikchr">', m.pikchr, '</verbatim>'].join(''); break; default: body = m.result; } this.e.outText.value = body; this.e.outText.classList.remove('hidden'); pikOut.classList.add('hidden'); this.e.pikOutWrapper.classList.add('text'); break; } case 'svg': this.e.outText.classList.add('hidden'); pikOut.classList.remove('hidden'); this.e.pikOutWrapper.classList.remove('text'); pikOut.innerHTML = m.result; this.e.outText.value = m.result/*for clipboard copy*/; break; default: throw new Error("Unhandled render mode: "+mode); } let vw = null, vh = null; if('svg'===mode){ if(m.isError){ vw = vh = '100%'; }else if(this.config.renderAutofit){ /* FIXME: current behavior doesn't work as desired when width>height (e.g. non-side-by-side mode).*/ vw = vh = '98%'; }else{ vw = m.width+1+'px'; vh = m.height+1+'px'; /* +1 is b/c the SVG uses floating point sizes but pikchr() returns truncated integers. */ } pikOut.style.width = vw; pikOut.style.height = vh; } }.bind(PS))/*'pikchr' msg handler*/; E('#btn-render-mode').addEventListener('click',function(){ const modes = this.renderModes; modes.selectedIndex = (modes.selectedIndex + 1) % modes.length; this.e.previewModeLabel.innerText = this.renderModeLabels[modes[modes.selectedIndex]]; if(this.e.pikOut.dataset.pikchr){ this.render(this.e.pikOut.dataset.pikchr); } }.bind(PS)); F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText}); PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false); PS.addMsgHandler('working',function f(ev){ switch(ev.data){ case 'start': /* See notes in preStartWork(). */; return; case 'end': //preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig; this.e.btnRender.removeAttribute('disabled'); this.e.pikOutWrapper.classList[this.config.renderAutofit ? 'add' : 'remove']('autofit'); return; } console.warn("Unhandled 'working' event:",ev.data); }.bind(PS)); /* For each checkbox with data-csstgt, set up a handler which toggles the given CSS class on the element matching E(data-csstgt). */ EAll('input[type=checkbox][data-csstgt]') .forEach(function(e){ const tgt = E(e.dataset.csstgt); const cssClass = e.dataset.cssclass || 'error'; e.checked = tgt.classList.contains(cssClass); e.addEventListener('change', function(){ tgt.classList[ this.checked ? 'add' : 'remove' ](cssClass) }, false); }); /* For each checkbox with data-config=X, set up a binding to PS.config[X]. These must be set up AFTER data-csstgt checkboxes so that those two states can be synced properly. */ EAll('input[type=checkbox][data-config]') .forEach(function(e){ const confVal = !!PS.config[e.dataset.config]; if(e.checked !== confVal){ /* Ensure that data-csstgt mappings (if any) get synced properly. */ e.checked = confVal; e.dispatchEvent(new Event('change')); } e.addEventListener('change', function(){ PS.config[this.dataset.config] = this.checked; PS.storeConfig(); }, false); }); E('#opt-cb-autofit').addEventListener('change',function(){ /* PS.config.renderAutofit was set by the data-config event handler. */ if(0==PS.renderModes.selectedIndex && PS.e.pikOut.dataset.pikchr){ PS.render(PS.e.pikOut.dataset.pikchr); } }); /* For each button with data-cmd=X, map a click handler which calls PS.render(X). */ const cmdClick = function(){PS.render(this.dataset.cmd);}; EAll('button[data-cmd]').forEach( e => e.addEventListener('click', cmdClick, false) ); //////////////////////////////////////////////////////////// // Set up selection list of predefined scripts... if(true){ const selectScript = PS.e.selectScript = D.select(); D.append(PS.e.zoneInputButtons, selectScript); PS.predefinedPiks.forEach(function(script,ndx){ const opt = D.option(script.code ? script.code.trim() :'', script.name); D.append(selectScript, opt); if(!ndx) selectScript.selectedIndex = 0 /*timing/ordering workaround*/; if(ndx && !script.code){ /* Treat entries w/ no code as separators EXCEPT for the first one, which we want to keep selectable solely for cosmetic reasons. */ D.disable(opt); } }); delete PS.predefinedPiks; selectScript.addEventListener('change', function(ev){ const val = ev.target.value; if(!val) return; setCurrentText(val); }, false); }/*Examples*/ /** TODO: Handle load/import of an external pikchr file. */ if(0) E('#load-pikchr').addEventListener('change',function(){ const f = this.files[0]; const r = new FileReader(); const status = {loaded: 0, total: 0}; this.setAttribute('disabled','disabled'); const that = this; r.addEventListener('load', function(){ that.removeAttribute('disabled'); stdout("Loaded",f.name+". Opening pikchr..."); PS.wMsg('open',{ filename: f.name, buffer: this.result }); }); r.addEventListener('error',function(){ that.removeAttribute('disabled'); stderr("Loading",f.name,"failed for unknown reasons."); }); r.addEventListener('abort',function(){ that.removeAttribute('disabled'); stdout("Cancelled loading of",f.name+"."); }); r.readAsArrayBuffer(f); }); EAll('fieldset.collapsible').forEach(function(fs){ const btnToggle = E(fs,'legend > #btn-options-toggle'), content = EAll(fs,':scope > div'); btnToggle.addEventListener('click', function(){ fs.classList.toggle('collapsed'); content.forEach((d)=>d.classList.toggle('hidden')); }, false); }); PS.e.btnRender.click(); /** Debounce handler for auto-rendering while typing. */ const debounceAutoRender = F.debounce(function f(){ if(!PS._isDirty) return; const text = getCurrentText(); if(f._ === text){ PS._isDirty = false; return; } f._ = text; PS._isDirty = false; PS.render(text || ''); }, 800, false); taInput.addEventListener('keydown',function f(ev){ if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){ // Ctrl-enter and shift-enter both run the current input PS._isDirty = false/*prevent a pending debounce from re-rendering*/; ev.preventDefault(); ev.stopPropagation(); renderCurrentText(); return; } if(!PS.config.renderWhileTyping) return; /* Auto-render while typing... */ switch(ev.keyCode){ case (ev.keyCode<32): /*any ctrl char*/ /* ^^^ w/o that, simply tapping ctrl is enough to force a re-render. Similarly, TAB-ing focus away should not re-render. */ case 33: case 34: /* page up/down */ case 35: case 36: /* home/end */ case 37: case 38: case 39: case 40: /* arrows */ return; } PS._isDirty = true; debounceAutoRender(); }, false); const ForceResizeKludge = (function(){ /* Workaround for Safari mayhem regarding use of vh CSS units.... We cannot use vh units to set the main view size because Safari chokes on that, so we calculate that height here. Larger than ~95% is too big for Firefox on Android, causing the input area to move off-screen. */ const appViews = EAll('.app-view'); const elemsToCount = [ /* Elements which we need to always count in the visible body size. */ E('body > div.header'), E('body > div.mainmenu'), E('body > div.footer') ]; const resized = function f(){ if(f.$disabled) return; const wh = window.innerHeight; var ht; var extra = 0; elemsToCount.forEach((e)=>e ? extra += F.dom.effectiveHeight(e) : false); ht = wh - extra; appViews.forEach(function(e){ e.style.height = e.style.maxHeight = [ "calc(", (ht>=100 ? ht : 100), "px", " - 2em"/*fudge value*/,")" /* ^^^^ hypothetically not needed, but both Chrome/FF on Linux will force scrollbars on the body if this value is too small. */ ].join(''); }); }; resized.$disabled = true/*gets deleted when setup is finished*/; window.addEventListener('resize', F.debounce(resized, 250), false); return resized; })()/*ForceResizeKludge*/; delete ForceResizeKludge.$disabled; ForceResizeKludge(); }/*onPikchrshowLoaded()*/; /** Predefined scripts. Each entry is an object: { name: required string, code: optional code string. An entry with a falsy code is treated like a separator in the resulting SELECT element (a disabled OPTION). } */ PS.predefinedPiks = [ {name: "-- Example Scripts --", code: false}, /* The following were imported from the pikchr test scripts: https://fossil-scm.org/pikchr/dir/examples */ {name:"Cardinal headings",code:` linerad = 5px C: circle "Center" rad 150% circle "N" at 1.0 n of C; arrow from C to last chop -> circle "NE" at 1.0 ne of C; arrow from C to last chop <- circle "E" at 1.0 e of C; arrow from C to last chop <-> circle "SE" at 1.0 se of C; arrow from C to last chop -> circle "S" at 1.0 s of C; arrow from C to last chop <- circle "SW" at 1.0 sw of C; arrow from C to last chop <-> circle "W" at 1.0 w of C; arrow from C to last chop -> circle "NW" at 1.0 nw of C; arrow from C to last chop <- arrow from 2nd circle to 3rd circle chop arrow from 4th circle to 3rd circle chop arrow from SW to S chop <-> circle "ESE" at 2.0 heading 112.5 from Center \ thickness 150% fill lightblue radius 75% arrow from Center to ESE thickness 150% <-> chop arrow from ESE up 1.35 then to NE chop line dashed <- from E.e to (ESE.x,E.y) line dotted <-> thickness 50% from N to NW chop `},{name:"Core object types",code:`AllObjects: [ # First row of objects box "box" box rad 10px "box (with" "rounded" "corners)" at 1in right of previous circle "circle" at 1in right of previous ellipse "ellipse" at 1in right of previous # second row of objects OVAL1: oval "oval" at 1in below first box oval "(tall &" "thin)" "oval" width OVAL1.height height OVAL1.width \ at 1in right of previous cylinder "cylinder" at 1in right of previous file "file" at 1in right of previous # third row shows line-type objects dot "dot" above at 1in below first oval line right from 1.8cm right of previous "lines" above arrow right from 1.8cm right of previous "arrows" above spline from 1.8cm right of previous \ go right .15 then .3 heading 30 then .5 heading 160 then .4 heading 20 \ then right .15 "splines" at 3rd vertex of previous # The third vertex of the spline is not actually on the drawn # curve. The third vertex is a control point. To see its actual # position, uncomment the following line: #dot color red at 3rd vertex of previous spline # Draw various lines below the first line line dashed right from 0.3cm below start of previous line line dotted right from 0.3cm below start of previous line thin right from 0.3cm below start of previous line thick right from 0.3cm below start of previous # Draw arrows with different arrowhead configurations below # the first arrow arrow <- right from 0.4cm below start of previous arrow arrow <-> right from 0.4cm below start of previous # Draw splines with different arrowhead configurations below # the first spline spline same from .4cm below start of first spline -> spline same from .4cm below start of previous <- spline same from .4cm below start of previous <-> ] # end of AllObjects # Label the whole diagram text "Examples Of Pikchr Objects" big bold at .8cm above north of AllObjects `},{name:"Swimlanes",code:` $laneh = 0.75 # Draw the lanes down box width 3.5in height $laneh fill 0xacc9e3 box same fill 0xc5d8ef box same as first box box same as 2nd box line from 1st box.sw+(0.2,0) up until even with 1st box.n \ "Alan" above aligned line from 2nd box.sw+(0.2,0) up until even with 2nd box.n \ "Betty" above aligned line from 3rd box.sw+(0.2,0) up until even with 3rd box.n \ "Charlie" above aligned line from 4th box.sw+(0.2,0) up until even with 4th box.n \ "Darlene" above aligned # fill in content for the Alice lane right A1: circle rad 0.1in at end of first line + (0.2,-0.2) \ fill white thickness 1.5px "1" arrow right 50% circle same "2" arrow right until even with first box.e - (0.65,0.0) ellipse "future" fit fill white height 0.2 width 0.5 thickness 1.5px A3: circle same at A1+(0.8,-0.3) "3" fill 0xc0c0c0 arrow from A1 to last circle chop "fork!" below aligned # content for the Betty lane B1: circle same as A1 at A1-(0,$laneh) "1" arrow right 50% circle same "2" arrow right until even with first ellipse.w ellipse same "future" B3: circle same at A3-(0,$laneh) "3" arrow right 50% circle same as A3 "4" arrow from B1 to 2nd last circle chop # content for the Charlie lane C1: circle same as A1 at B1-(0,$laneh) "1" arrow 50% circle same "2" arrow right 0.8in "goes" "offline" C5: circle same as A3 "5" arrow right until even with first ellipse.w \ "back online" above "pushes 5" below "pulls 3 & 4" below ellipse same "future" # content for the Darlene lane D1: circle same as A1 at C1-(0,$laneh) "1" arrow 50% circle same "2" arrow right until even with C5.w circle same "5" arrow 50% circle same as A3 "6" arrow right until even with first ellipse.w ellipse same "future" D3: circle same as B3 at B3-(0,2*$laneh) "3" arrow 50% circle same "4" arrow from D1 to D3 chop `},{ name: "The Stuff of Dreams", code:` O: text "DREAMS" color grey circle rad 0.9 at 0.6 above O thick color red text "INEXPENSIVE" big bold at 0.9 above O color red circle rad 0.9 at 0.6 heading 120 from O thick color green text "FAST" big bold at 0.9 heading 120 from O color green circle rad 0.9 at 0.6 heading -120 from O thick color blue text "HIGH" big bold "QUALITY" big bold at 0.9 heading -120 from O color blue text "EXPENSIVE" at 0.55 below O color cyan text "SLOW" at 0.55 heading -60 from O color magenta text "POOR" "QUALITY" at 0.55 heading 60 from O color gold `} ]; })(window.fossil); |
Changes to src/json.c.
︙ | ︙ | |||
556 557 558 559 560 561 562 | } /* ** Guesses a RESPONSE Content-Type value based (primarily) on the ** HTTP_ACCEPT header. ** ** It will try to figure out if the client can support | | | | | 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 | } /* ** Guesses a RESPONSE Content-Type value based (primarily) on the ** HTTP_ACCEPT header. ** ** It will try to figure out if the client can support ** application/json, text/javascript, and will fall back to ** text/plain if it cannot figure out anything more specific. ** ** Returned memory is static and immutable, but if the environment ** changes after calling this then subsequent calls to this function ** might return different (also static/immutable) values. */ char const * json_guess_content_type(){ char const * cset; char doUtf8; cset = PD("HTTP_ACCEPT_CHARSET",NULL); doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset))) ? 1 : 0; if( g.json.jsonp ){ return doUtf8 ? "text/javascript; charset=utf-8" : "text/javascript"; }else{ /* Content-type If the browser does not sent an ACCEPT for application/json then we fall back to text/plain. */ |
︙ | ︙ | |||
603 604 605 606 607 608 609 | } } /* ** Given a request CONTENT_TYPE value, this function returns true ** if it is of a type which the JSON API can ostensibly read. ** | | | | | > | > | 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 | } } /* ** Given a request CONTENT_TYPE value, this function returns true ** if it is of a type which the JSON API can ostensibly read. ** ** It accepts any of application/json, text/plain, ** application/javascript, or text/javascript. The former is ** preferred, but was not widespread when this API was initially ** built, so the latter forms are permitted as fallbacks. */ int json_can_consume_content_type(const char * zType){ return fossil_strcmp(zType, "application/json")==0 || fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/ || fossil_strcmp(zType,"text/javascript")==0 || fossil_strcmp(zType,"application/javascript")==0; } /* ** Sends pResponse to the output stream as the response object. This ** function does no validation of pResponse except to assert() that it ** is not NULL. The caller is responsible for ensuring that it meets ** API response envelope conventions. ** ** In CLI mode pResponse is sent to stdout immediately. In HTTP ** mode pResponse replaces any current CGI content but cgi_reply() ** is not called to flush the output. ** ** If g.json.jsonp is not NULL then the content type is set to ** text/javascript and the output is wrapped in a jsonp ** wrapper. */ void json_send_response( cson_value const * pResponse ){ assert( NULL != pResponse ); if( g.isHTTP ){ cgi_reset_content(); if( g.json.jsonp ){ cgi_set_content_type("text/javascript"); cgi_printf("%s(",g.json.jsonp); } cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt ); if( g.json.jsonp ){ cgi_append_content(")",1); } }else{/*CLI mode*/ |
︙ | ︙ |
Changes to src/main.mk.
︙ | ︙ | |||
161 162 163 164 165 166 167 168 169 170 171 172 173 174 | $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c EXTRA_FILES = \ $(SRCDIR)/../skins/ardoise/css.txt \ $(SRCDIR)/../skins/ardoise/details.txt \ $(SRCDIR)/../skins/ardoise/footer.txt \ $(SRCDIR)/../skins/ardoise/header.txt \ $(SRCDIR)/../skins/black_and_white/css.txt \ $(SRCDIR)/../skins/black_and_white/details.txt \ $(SRCDIR)/../skins/black_and_white/footer.txt \ | > > > | 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c EXTRA_FILES = \ $(SRCDIR)/../extsrc/pikchr-worker.js \ $(SRCDIR)/../extsrc/pikchr.js \ $(SRCDIR)/../extsrc/pikchr.wasm \ $(SRCDIR)/../skins/ardoise/css.txt \ $(SRCDIR)/../skins/ardoise/details.txt \ $(SRCDIR)/../skins/ardoise/footer.txt \ $(SRCDIR)/../skins/ardoise/header.txt \ $(SRCDIR)/../skins/black_and_white/css.txt \ $(SRCDIR)/../skins/black_and_white/details.txt \ $(SRCDIR)/../skins/black_and_white/footer.txt \ |
︙ | ︙ | |||
229 230 231 232 233 234 235 236 237 238 239 240 241 242 | $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.brlist.js \ $(SRCDIR)/fossil.page.chat.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.pikchrshow.js \ $(SRCDIR)/fossil.page.whistory.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.pikchr.js \ $(SRCDIR)/fossil.popupwidget.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/fossil.wikiedit-wysiwyg.js \ | > | 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.brlist.js \ $(SRCDIR)/fossil.page.chat.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.pikchrshow.js \ $(SRCDIR)/fossil.page.pikchrshowasm.js \ $(SRCDIR)/fossil.page.whistory.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.pikchr.js \ $(SRCDIR)/fossil.popupwidget.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/fossil.wikiedit-wysiwyg.js \ |
︙ | ︙ | |||
264 265 266 267 268 269 270 271 272 273 274 275 276 277 | $(SRCDIR)/sounds/c.wav \ $(SRCDIR)/sounds/d.wav \ $(SRCDIR)/sounds/e.wav \ $(SRCDIR)/sounds/f.wav \ $(SRCDIR)/style.admin_log.css \ $(SRCDIR)/style.chat.css \ $(SRCDIR)/style.fileedit.css \ $(SRCDIR)/style.wikiedit.css \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ | > | 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | $(SRCDIR)/sounds/c.wav \ $(SRCDIR)/sounds/d.wav \ $(SRCDIR)/sounds/e.wav \ $(SRCDIR)/sounds/f.wav \ $(SRCDIR)/style.admin_log.css \ $(SRCDIR)/style.chat.css \ $(SRCDIR)/style.fileedit.css \ $(SRCDIR)/style.pikchrshow.css \ $(SRCDIR)/style.wikiedit.css \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ |
︙ | ︙ | |||
2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 | $(OBJDIR)/pikchr.o: $(SRCDIR_extsrc)/pikchr.c $(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@ $(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c $(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@ # # The list of all the targets that do not correspond to real files. This stops # 'make' from getting confused when someone makes an error in a rule. # .PHONY: all install test clean | > > > > > | 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 | $(OBJDIR)/pikchr.o: $(SRCDIR_extsrc)/pikchr.c $(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@ $(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c $(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@ $(SRCDIR_extsrc)/pikchr.js: $(SRCDIR_extsrc)/pikchr.c $(EMCC_WRAPPER) -o $@ $(EMCC_OPT) --no-entry -sEXPORTED_RUNTIME_METHODS=cwrap,setValue,getValue,stackSave,stackRestore -sEXPORTED_FUNCTIONS=_pikchr $(SRCDIR_extsrc)/pikchr.c -sENVIRONMENT=web -sMODULARIZE -sEXPORT_NAME=initPikchrModule --minify 0 @chmod -x $(SRCDIR_extsrc)/pikchr.wasm wasm: $(SRCDIR_extsrc)/pikchr.js # # The list of all the targets that do not correspond to real files. This stops # 'make' from getting confused when someone makes an error in a rule. # .PHONY: all install test clean |
Changes to src/pikchrshow.c.
︙ | ︙ | |||
228 229 230 231 232 233 234 | blob_appendf(pOut, "%s\n", zNonce); } blob_reset(&bIn); return isErr; } /* | | > | > > > | < < | | > > > > | 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | blob_appendf(pOut, "%s\n", zNonce); } blob_reset(&bIn); return isErr; } /* ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to ** this one if the "legacy" or "ajax" request arguments are set. ** ** A pikchr code editor and previewer, allowing users to experiment ** with pikchr code or prototype it for use in copy/pasting into forum ** posts, wiki pages, or embedded docs. This version of pikchrshow ** uses JavaScript to send pikchr code to the server for ** processing. The newer /pikchrshow applications runs pikchr on the ** client machine, without the need for back-and-forth network ** traffic. */ void pikchrshowcs_page(void){ const char *zContent = 0; int isDark; /* true if the current skin is "dark" */ int pikFlags = PIKCHR_PROCESS_DIV | PIKCHR_PROCESS_SRC | PIKCHR_PROCESS_ERR_PRE; login_check_credentials(); if( !g.perm.RdWiki && !g.perm.Read && !g.perm.RdForum ){ cgi_redirectf("%R/login?g=pikchrshowcs"); } if(P("wasm")){ pikchrshow_page(); return; } zContent = PD("content",P("p")); if(P("ajax")!=0){ /* Called from the JS-side preview updater. TODO: respond with JSON instead.*/ cgi_set_content_type("text/html"); if(zContent && *zContent){ |
︙ | ︙ | |||
277 278 279 280 281 282 283 | if(!zContent){ zContent = "arrow right 200% \"Markdown\" \"Source\"\n" "box rad 10px \"Markdown\" \"Formatter\" \"(markdown.c)\" fit\n" "arrow right 200% \"HTML+SVG\" \"Output\"\n" "arrow <-> down from last box.s\n" "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n"; } | | | 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | if(!zContent){ zContent = "arrow right 200% \"Markdown\" \"Source\"\n" "box rad 10px \"Markdown\" \"Formatter\" \"(markdown.c)\" fit\n" "arrow right 200% \"HTML+SVG\" \"Output\"\n" "arrow <-> down from last box.s\n" "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n"; } style_header("PikchrShow Client/Server"); CX("<style>"); { CX("div.content { padding-top: 0.5em }\n"); CX("#sbs-wrapper {" "display: flex; flex-direction: column;" "}\n"); CX("#sbs-wrapper > * {" "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;" |
︙ | ︙ | |||
334 335 336 337 338 339 340 | "}\n"); CX("body.pikchrshow .v-align-middle{" "vertical-align: middle" "}\n"); CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n"); } CX("</style>"); CX("<div>Input pikchr code and tap Preview (or Shift-Enter) to render " | | | 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 | "}\n"); CX("body.pikchrshow .v-align-middle{" "vertical-align: middle" "}\n"); CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n"); } CX("</style>"); CX("<div>Input pikchr code and tap Preview (or Shift-Enter) to render " "it. <a href='?wasm'>Switch to WASM mode</a>.</div>"); CX("<div id='sbs-wrapper'>"); { CX("<div id='pikchrshow-form'>"); { CX("<textarea id='content' name='content' rows='15'>" "%s</textarea>",zContent/*safe-for-%s*/); CX("<div id='pikchrshow-controls'>"); { CX("<button id='pikchr-submit-preview'>Preview</button>"); CX("<div class='input-with-label'>"); { |
︙ | ︙ | |||
374 375 376 377 378 379 380 381 382 383 384 385 386 387 | } CX("</div>"/*sbs-wrapper*/); builtin_fossil_js_bundle_or("fetch", "copybutton", "popupwidget", "storage", "pikchr", NULL); builtin_request_js("fossil.page.pikchrshow.js"); builtin_fulfill_js_requests(); style_finish_page(); } /* ** COMMAND: pikchr* ** ** Usage: %fossil pikchr [options] ?INFILE? ?OUTFILE? ** ** Accepts a pikchr script as input and outputs the rendered script as | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 | } CX("</div>"/*sbs-wrapper*/); builtin_fossil_js_bundle_or("fetch", "copybutton", "popupwidget", "storage", "pikchr", NULL); builtin_request_js("fossil.page.pikchrshow.js"); builtin_fulfill_js_requests(); style_finish_page(); } /* ** WEBPAGE: pikchrshow ** ** A pikchr code editor and previewer, allowing users to experiment ** with pikchr code or prototype it for use in copy/pasting into forum ** posts, wiki pages, or embedded docs. This version of pikchrshow ** uses WebAssembly to run entirely in the client browser, without a ** need for back-and-forth client/server traffic to perform the ** rendering. The "legacy" version of this application, which sends ** all input to the server for rendering, can be accessed by adding ** the "legacy" URL argument. ** ** It optionally accepts a p=pikchr-script-code URL parameter or POST ** value to pre-populate the editor with that code. */ void pikchrshow_page(void){ const char *zContent = 0; if(P("legacy") || P("ajax")){ pikchrshowcs_page(); return; } login_check_credentials(); if( !g.perm.RdWiki && !g.perm.Read && !g.perm.RdForum ){ cgi_redirectf("%R/login?g=pikchrshow"); } style_emit_noscript_for_js_page(); style_header("PikchrShow"); zContent = PD("content",P("p")); if(!zContent){ zContent = "arrow right 200% \"Markdown\" \"Source\"\n" "box rad 10px \"Markdown\" \"Formatter\" \"(markdown.c)\" fit\n" "arrow right 200% \"HTML+SVG\" \"Output\"\n" "arrow <-> down from last box.s\n" "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n"; } /* Wasm load/init progress widget... */ CX("<div class='emscripten'>"); { CX("<figure id='module-spinner'>"); CX("<div class='spinner'></div>"); CX("<div class='center'><strong>Initializing app...</strong></div>"); CX("<div class='center'>"); CX("On a slow internet connection this may take a moment. If this "); CX("message displays for \"a long time\", intialization may have "); CX("failed and the JavaScript console may contain clues as to why. "); CX("</div>"); CX("<div><a href='?legacy'>Switch to legacy mode</a></div>"); CX("</figure>"); CX("<div class='emscripten' id='module-status'>Downloading...</div>"); CX("<progress value='0' max='100' id='module-progress' hidden='1'>" "</progress>"); } CX("</div><!-- .emscripten -->"); /* Main view... */ CX("<div id='view-split' class='app-view initially-hidden'>"); { CX("<fieldset class='options collapsible'>"); { CX("<legend><button id='btn-options-toggle'>Options</button></legend>"); CX("<div>"); CX("<span class='labeled-input'>"); CX("<input type='checkbox' id='opt-cb-sbs' "); CX("data-csstgt='#main-wrapper' "); CX("data-cssclass='side-by-side' "); CX("data-config='sideBySide'>"); CX("<label for='opt-cb-sbs'>Side-by-side</label>"); CX("</span>"); CX("<span class='labeled-input'>"); CX("<input type='checkbox' id='opt-cb-swapio' "); CX("data-csstgt='#main-wrapper' "); CX("data-cssclass='swapio' "); CX("data-config='swapInOut'>"); CX("<label for='opt-cb-swapio'>Swap in/out</label>"); CX("</span>"); CX("<span class='labeled-input'>"); CX("<input type='checkbox' id='opt-cb-autofit' "); CX("data-config='renderAutofit'>"); CX("<label for='opt-cb-autofit' " "title='Attempt to scale SVG to fit viewport. " "Whether it will work depends in part on the size " "and shape of the image and the viewport.'" ">Auto-fit SVG</label>"); CX("</span>"); CX("<span class='labeled-input'>"); CX("<input type='checkbox' id='opt-cb-autorender' "); CX("data-csstgt='#main-wrapper' "); CX("data-cssclass='auto-render' "); CX("data-config='renderWhileTyping'>"); CX("<label for='opt-cb-autorender'>Render while typing</label>"); CX("</span>"); CX("<span class='labeled-input'>"); CX("<a href='?legacy'>Legacy mode</a>"); CX("</span>"); CX("</div><!-- options wrapper -->"); } CX("</fieldset>"); CX("<div id='main-wrapper' class=''>"); { CX("<fieldset class='zone-wrapper input'>"); { CX("<legend><div class='button-bar'>"); CX("<button id='btn-render' " "title='Ctrl-Enter/Shift-Enter'>Render</button>"); CX("<button id='btn-clear'>Clear Input</button>"); CX("</div></legend>"); CX("<div><textarea id='input'"); CX("placeholder='Pikchr input. Ctrl-enter/shift-enter runs it.'>"); CX("/**\n"); CX(" Use ctrl-enter or shift-enter to execute\n"); CX(" pikchr code. If only a subset is currently\n"); CX(" selected, only that part is evaluated.\n*/\n"); CX("%s</textarea></div>",zContent/*safe-for-%s*/); } CX("</fieldset><!-- .zone-wrapper.input -->"); /*CX("<div class='splitter-handle hidden'></div>");*/ CX("<fieldset class='zone-wrapper output'>"); { CX("<legend><div class='button-bar'>"); CX("<button id='btn-render-mode'>Render Mode</button> "); CX("<span style='white-space:nowrap'>" "<span id='preview-copy-button' " "title='Tap to copy to clipboard.'></span>" "<label for='preview-copy-button' " "title='Tap to copy to clipboard.'></label>" "</span>"); CX("</div></legend>"); CX("<div id='pikchr-output-wrapper'>"); CX("<div id='pikchr-output'></div>"); CX("<textarea class='hidden' id='pikchr-output-text'></textarea>"); CX("</div>"); } CX("</fieldset> <!-- .zone-wrapper.output -->"); } CX("</div><!-- #main-wrapper -->"); } CX("</div><!-- #view-split -->"); builtin_fossil_js_bundle_or("dom", "storage", "copybutton", NULL); builtin_request_js("fossil.page.pikchrshowasm.js"); builtin_fulfill_js_requests(); style_finish_page(); } /* ** COMMAND: pikchr* ** ** Usage: %fossil pikchr [options] ?INFILE? ?OUTFILE? ** ** Accepts a pikchr script as input and outputs the rendered script as |
︙ | ︙ |
Changes to src/style.c.
︙ | ︙ | |||
899 900 901 902 903 904 905 | ** Generate code to load all required javascript files. */ static void style_load_all_js_files(void){ if( needHrefJs && g.perm.Hyperlink ){ int nDelay = db_get_int("auto-hyperlink-delay",0); int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0) && sqlite3_strglob("*Android*",PD("HTTP_USER_AGENT","")); | | | 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 | ** Generate code to load all required javascript files. */ static void style_load_all_js_files(void){ if( needHrefJs && g.perm.Hyperlink ){ int nDelay = db_get_int("auto-hyperlink-delay",0); int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0) && sqlite3_strglob("*Android*",PD("HTTP_USER_AGENT","")); @ <script id='href-data' type='text/json'>\ @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script> } @ <script nonce="%h(style_nonce())">/* style.c:%d(__LINE__) */ @ function debugMsg(msg){ @ var n = document.getElementById("debugMsg"); @ if(n){n.textContent=msg;} @ } |
︙ | ︙ | |||
1218 1219 1220 1221 1222 1223 1224 | const char *zScript = skin_get("js"); if( P("test") ){ /* Render the script as plain-text for testing purposes, if the "test" ** query parameter is present */ cgi_set_content_type("text/plain"); }else{ /* Default behavior is to return javascript */ | | | 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 | const char *zScript = skin_get("js"); if( P("test") ){ /* Render the script as plain-text for testing purposes, if the "test" ** query parameter is present */ cgi_set_content_type("text/plain"); }else{ /* Default behavior is to return javascript */ cgi_set_content_type("text/javascript"); } style_init_th1_vars(0); Th_Render(zScript?zScript:""); } /* ** Check for "name" or "page" query parameters on an /style.css |
︙ | ︙ |
Added src/style.pikchrshow.css.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | /* CSS for the WASM /pikchrshow app. */ /* emcscript-related styling, used during the module load/intialization processes... */ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } div.emscripten { text-align: center; } div.emscripten_border { border: 1px solid black; } #module-spinner { overflow: visible; } #module-spinner > * { margin-top: 1em; } .spinner { height: 50px; width: 50px; margin: 0px auto; animation: rotation 0.8s linear infinite; border-left: 10px solid rgb(0,150,240); border-right: 10px solid rgb(0,150,240); border-bottom: 10px solid rgb(0,150,240); border-top: 10px solid rgb(100,0,200); border-radius: 100%; background-color: rgb(200,100,250); } @keyframes rotation { from {transform: rotate(0deg);} to {transform: rotate(360deg);} } /* The following styles are for app-level use. */ /* TODO: consolidate the WASM- and legacy-mode CSS into this file. Since they share a URI, they both load this file. */ textarea { font-family: monospace; flex: 1 1 auto; } #main-wrapper { display: flex; flex-direction: column-reverse; flex: 1 1 auto; margin: 0.5em 0; overflow: hidden; } #main-wrapper.side-by-side { flex-direction: row; } #main-wrapper.side-by-side > fieldset { margin-left: 0.25em; margin-right: 0.25em; } #main-wrapper:not(.side-by-side) > fieldset { margin-bottom: 0.25em; } #main-wrapper.swapio { flex-direction: column; } #main-wrapper.side-by-side.swapio { flex-direction: row-reverse; } #main-wrapper .splitter-handle { border: 1px outset; border-radius: 0.25em; } #main-wrapper.side-by-side .splitter-handle { width: 2px; } #main-wrapper:not(.side-by-side) .splitter-handle { width: 100%; height: 2px; } .zone-wrapper{ display: flex; margin: 0; flex: 1 1 0%; border-radius: 0.5em; min-width: inherit/*important: resolves inability to scroll fieldset child element!*/; padding: 0.35em 0 0 0; } .zone-wrapper > div { display:flex; flex-direction: column; align-items: stretch; flex: 1 1 0%; } .zone-wrapper.output { } #main-wrapper.side-by-side .zone-wrapper { /* Keep the layout from "flopping around" when render-while-typing mode is on. */ /*max-width: 50%;*/ } #pikchr-output { padding: 0; margin: 0 auto/*auto resolves a weird left-shift truncation of the SVG*/; } #pikchr-output-wrapper { flex: 1 1 auto; overflow: auto; } #pikchr-output-wrapper:not(.autofit) { align-items: center; } #pikchr-output-wrapper.text { display: flex; align-items: stretch; } #pikchr-output-wrapper.text > #pikchr-output { display: flex; align-items: stretch; flex: 1 1 auto; } #pikchr-output-wrapper.text > #pikchr-output > textarea { flex: 1 1 auto; } fieldset > legend { font-size: 85%; } .zone-wrapper textarea { border-radius: 0.5em; flex: 1 1 auto; /*min/max width resolve an inexplicable margin on the RHS. The -1em is for the padding, else we overlap the parent boundaries.*/ min-width: calc(100% - 1em); max-width: calc(100% - 1em); padding: 0 0.5em; } .zone-wrapper.input { min-height: 10em; /*width: 50%;*/ min-width: 20em; } .zone-wrapper textarea { border: 0; outline: 0; } .zone-wrapper.output { overflow: auto; justify-content: space-between; } .button-bar { /*display: flex; flex-wrap: wrap; align-items: center;*/ } .button-bar > * { margin-top: 0.1em/*avoid crowding if the controls wrap*/; vertical-align: middle; } .button-bar > * + * /*all children except the first*/ { margin-left: 0.65em; } fieldset.options { border-radius: 0.5em; border: 1px inset; display: flex; flex-direction: column; padding: 0.25em; } fieldset.options > div { display: flex; flex-wrap: wrap; font-size: 85%; } fieldset.options > legend > #btn-options-toggle::after { content: " [hide]"; position: relative; } fieldset.options.collapsed > legend > #btn-options-toggle::after { content: " [show]"; position: relative; } span.labeled-input { padding: 0.25em; margin: 0.25em 0.5em; border-radius: 0.25em; white-space: nowrap; background: #0002; display: flex; align-items: center; } span.labeled-input > *:nth-child(2) { margin-left: 0.3em; } .center { text-align: center; } .app-view { flex: 20 1 auto; } #titlebar { display: flex; justify-content: space-between; margin-bottom: 0.5em; } #view-split { display: flex; flex-direction: column-reverse; } |
Added tools/emcc.sh.in.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #!/usr/bin/bash ######################################################################## # WARNING: emcc.sh is generated from emcc.sh.in by the configure # process. Do not edit emcc.sh directly, as it may be deleted or # overwritten by the configure script. # # A wrapper around the emcc compiler which uses configure-time state # to locate the Emscripten SDK and import the SDK's environment # script, if needed. ######################################################################## # EMSDK_HOME comes from the configure --with-emsdk=/dir flag. # EMSDK_ENV is ${thatDir}/emsdk_env.sh and is also set by the # configure process. EMSDK_HOME="@EMSDK_HOME@" EMSDK_ENV="@EMSDK_ENV@" emcc=$(which emcc 2>/dev/null) if [ x = "x${emcc}" ]; then # If emcc is not found in the path, try to find it via an emsdk # installation. The SDK variant is the official installation # style supported by the Emscripten folks, but emcc is also # available via package managers on some OSes. if [ x = "x${EMSDK_HOME}" ]; then echo "EMSDK_HOME is not set. Pass --with-emsdk=/path/to/emsdk" \ "to the configure script." 1>&2 exit 1 fi if [ x = "x${EMSDK_ENV}" ]; then if [ -f "${EMSDK_HOME}/emsdk_env.sh" ]; then EMSDK_ENV="${EMSDK_HOME}/emsdk_env.sh" else echo "EMSDK_ENV is not set. Expecting configure script to set it." 1>&2 exit 2 fi fi if [ ! -f "${EMSDK_ENV}" ]; then echo "emsdk_env script not found: $EMSDK_ENV" 1>&2 exit 3 fi # $EMSDK is part of the state set by emsdk_env.sh. if [ x = "x${EMSDK}" ]; then source "${EMSDK_ENV}" >/dev/null 2>&1 || { # ^^^ unfortunately outputs lots of noise to stderr rc=$? echo "Error sourcing ${EMSDK_ENV}" exit $rc } fi emcc=$(which emcc 2>/dev/null) if [ x = "x${emcc}" ]; then echo "emcc not found in PATH. Normally that's set up by EMSDK_ENV." 1>&2 exit 4 fi fi exec emcc "$@" |
Changes to tools/makemake.tcl.
︙ | ︙ | |||
215 216 217 218 219 220 221 222 223 224 225 226 227 228 | wiki.wiki *.js default.css style.*.css ../skins/*/*.txt sounds/*.wav alerts/*.wav } # Options used to compile the included SQLite library. # set SQLITE_OPTIONS { -DNDEBUG=1 -DSQLITE_DQS=0 | > > | 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | wiki.wiki *.js default.css style.*.css ../skins/*/*.txt sounds/*.wav alerts/*.wav ../extsrc/pikchr.wasm ../extsrc/pikchr*.js } # Options used to compile the included SQLite library. # set SQLITE_OPTIONS { -DNDEBUG=1 -DSQLITE_DQS=0 |
︙ | ︙ | |||
557 558 559 560 561 562 563 564 565 566 567 568 569 570 | writeln { $(OBJDIR)/pikchr.o: $(SRCDIR_extsrc)/pikchr.c $(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@ $(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c $(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@ # # The list of all the targets that do not correspond to real files. This stops # 'make' from getting confused when someone makes an error in a rule. # .PHONY: all install test clean } | > > > > > > > > > > > | 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 | writeln { $(OBJDIR)/pikchr.o: $(SRCDIR_extsrc)/pikchr.c $(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@ $(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c $(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@ $(SRCDIR_extsrc)/pikchr.js: $(SRCDIR_extsrc)/pikchr.c $(EMCC_WRAPPER) -o $@ $(EMCC_OPT) --no-entry \ -sEXPORTED_RUNTIME_METHODS=cwrap,setValue,getValue,stackSave,stackRestore \ -sEXPORTED_FUNCTIONS=_pikchr $(SRCDIR_extsrc)/pikchr.c \ -sENVIRONMENT=web \ -sMODULARIZE \ -sEXPORT_NAME=initPikchrModule \ --minify 0 @chmod -x $(SRCDIR_extsrc)/pikchr.wasm wasm: $(SRCDIR_extsrc)/pikchr.js # # The list of all the targets that do not correspond to real files. This stops # 'make' from getting confused when someone makes an error in a rule. # .PHONY: all install test clean } |
︙ | ︙ | |||
635 636 637 638 639 640 641 | # BCC = $(BCCEXE) #### Enable compiling with debug symbols (much larger binary) # # FOSSIL_ENABLE_SYMBOLS = 1 | | | 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 | # BCC = $(BCCEXE) #### Enable compiling with debug symbols (much larger binary) # # FOSSIL_ENABLE_SYMBOLS = 1 #### Enable JSON (https://www.json.org) support using "cson" # # FOSSIL_ENABLE_JSON = 1 #### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) # # FOSSIL_ENABLE_SSL = 1 |
︙ | ︙ |
Changes to win/Makefile.mingw.
︙ | ︙ | |||
52 53 54 55 56 57 58 | # BCC = $(BCCEXE) #### Enable compiling with debug symbols (much larger binary) # # FOSSIL_ENABLE_SYMBOLS = 1 | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | # BCC = $(BCCEXE) #### Enable compiling with debug symbols (much larger binary) # # FOSSIL_ENABLE_SYMBOLS = 1 #### Enable JSON (https://www.json.org) support using "cson" # # FOSSIL_ENABLE_JSON = 1 #### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) # # FOSSIL_ENABLE_SSL = 1 |
︙ | ︙ | |||
547 548 549 550 551 552 553 554 555 556 557 558 559 560 | $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c EXTRA_FILES = \ $(SRCDIR)/../skins/ardoise/css.txt \ $(SRCDIR)/../skins/ardoise/details.txt \ $(SRCDIR)/../skins/ardoise/footer.txt \ $(SRCDIR)/../skins/ardoise/header.txt \ $(SRCDIR)/../skins/black_and_white/css.txt \ $(SRCDIR)/../skins/black_and_white/details.txt \ $(SRCDIR)/../skins/black_and_white/footer.txt \ | > > > | 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 | $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c EXTRA_FILES = \ $(SRCDIR)/../extsrc/pikchr-worker.js \ $(SRCDIR)/../extsrc/pikchr.js \ $(SRCDIR)/../extsrc/pikchr.wasm \ $(SRCDIR)/../skins/ardoise/css.txt \ $(SRCDIR)/../skins/ardoise/details.txt \ $(SRCDIR)/../skins/ardoise/footer.txt \ $(SRCDIR)/../skins/ardoise/header.txt \ $(SRCDIR)/../skins/black_and_white/css.txt \ $(SRCDIR)/../skins/black_and_white/details.txt \ $(SRCDIR)/../skins/black_and_white/footer.txt \ |
︙ | ︙ | |||
615 616 617 618 619 620 621 622 623 624 625 626 627 628 | $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.brlist.js \ $(SRCDIR)/fossil.page.chat.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.pikchrshow.js \ $(SRCDIR)/fossil.page.whistory.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.pikchr.js \ $(SRCDIR)/fossil.popupwidget.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/fossil.wikiedit-wysiwyg.js \ | > | 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 | $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.numbered-lines.js \ $(SRCDIR)/fossil.page.brlist.js \ $(SRCDIR)/fossil.page.chat.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.page.forumpost.js \ $(SRCDIR)/fossil.page.pikchrshow.js \ $(SRCDIR)/fossil.page.pikchrshowasm.js \ $(SRCDIR)/fossil.page.whistory.js \ $(SRCDIR)/fossil.page.wikiedit.js \ $(SRCDIR)/fossil.pikchr.js \ $(SRCDIR)/fossil.popupwidget.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/fossil.wikiedit-wysiwyg.js \ |
︙ | ︙ | |||
650 651 652 653 654 655 656 657 658 659 660 661 662 663 | $(SRCDIR)/sounds/c.wav \ $(SRCDIR)/sounds/d.wav \ $(SRCDIR)/sounds/e.wav \ $(SRCDIR)/sounds/f.wav \ $(SRCDIR)/style.admin_log.css \ $(SRCDIR)/style.chat.css \ $(SRCDIR)/style.fileedit.css \ $(SRCDIR)/style.wikiedit.css \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ | > | 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 | $(SRCDIR)/sounds/c.wav \ $(SRCDIR)/sounds/d.wav \ $(SRCDIR)/sounds/e.wav \ $(SRCDIR)/sounds/f.wav \ $(SRCDIR)/style.admin_log.css \ $(SRCDIR)/style.chat.css \ $(SRCDIR)/style.fileedit.css \ $(SRCDIR)/style.pikchrshow.css \ $(SRCDIR)/style.wikiedit.css \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ |
︙ | ︙ |
Changes to win/Makefile.msc.
︙ | ︙ | |||
503 504 505 506 507 508 509 | "$(OX)\winfile_.c" \ "$(OX)\winhttp_.c" \ "$(OX)\xfer_.c" \ "$(OX)\xfersetup_.c" \ "$(OX)\zip_.c" \ "$(SRCDIR_extsrc)\pikchr.c" | > > > | | 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 | "$(OX)\winfile_.c" \ "$(OX)\winhttp_.c" \ "$(OX)\xfer_.c" \ "$(OX)\xfersetup_.c" \ "$(OX)\zip_.c" \ "$(SRCDIR_extsrc)\pikchr.c" EXTRA_FILES = "$(SRCDIR)\..\extsrc\pikchr-worker.js" \ "$(SRCDIR)\..\extsrc\pikchr.js" \ "$(SRCDIR)\..\extsrc\pikchr.wasm" \ "$(SRCDIR)\..\skins\ardoise\css.txt" \ "$(SRCDIR)\..\skins\ardoise\details.txt" \ "$(SRCDIR)\..\skins\ardoise\footer.txt" \ "$(SRCDIR)\..\skins\ardoise\header.txt" \ "$(SRCDIR)\..\skins\black_and_white\css.txt" \ "$(SRCDIR)\..\skins\black_and_white\details.txt" \ "$(SRCDIR)\..\skins\black_and_white\footer.txt" \ "$(SRCDIR)\..\skins\black_and_white\header.txt" \ |
︙ | ︙ | |||
571 572 573 574 575 576 577 578 579 580 581 582 583 584 | "$(SRCDIR)\fossil.fetch.js" \ "$(SRCDIR)\fossil.numbered-lines.js" \ "$(SRCDIR)\fossil.page.brlist.js" \ "$(SRCDIR)\fossil.page.chat.js" \ "$(SRCDIR)\fossil.page.fileedit.js" \ "$(SRCDIR)\fossil.page.forumpost.js" \ "$(SRCDIR)\fossil.page.pikchrshow.js" \ "$(SRCDIR)\fossil.page.whistory.js" \ "$(SRCDIR)\fossil.page.wikiedit.js" \ "$(SRCDIR)\fossil.pikchr.js" \ "$(SRCDIR)\fossil.popupwidget.js" \ "$(SRCDIR)\fossil.storage.js" \ "$(SRCDIR)\fossil.tabs.js" \ "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" \ | > | 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 | "$(SRCDIR)\fossil.fetch.js" \ "$(SRCDIR)\fossil.numbered-lines.js" \ "$(SRCDIR)\fossil.page.brlist.js" \ "$(SRCDIR)\fossil.page.chat.js" \ "$(SRCDIR)\fossil.page.fileedit.js" \ "$(SRCDIR)\fossil.page.forumpost.js" \ "$(SRCDIR)\fossil.page.pikchrshow.js" \ "$(SRCDIR)\fossil.page.pikchrshowasm.js" \ "$(SRCDIR)\fossil.page.whistory.js" \ "$(SRCDIR)\fossil.page.wikiedit.js" \ "$(SRCDIR)\fossil.pikchr.js" \ "$(SRCDIR)\fossil.popupwidget.js" \ "$(SRCDIR)\fossil.storage.js" \ "$(SRCDIR)\fossil.tabs.js" \ "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" \ |
︙ | ︙ | |||
606 607 608 609 610 611 612 613 614 615 616 617 618 619 | "$(SRCDIR)\sounds\c.wav" \ "$(SRCDIR)\sounds\d.wav" \ "$(SRCDIR)\sounds\e.wav" \ "$(SRCDIR)\sounds\f.wav" \ "$(SRCDIR)\style.admin_log.css" \ "$(SRCDIR)\style.chat.css" \ "$(SRCDIR)\style.fileedit.css" \ "$(SRCDIR)\style.wikiedit.css" \ "$(SRCDIR)\tree.js" \ "$(SRCDIR)\useredit.js" \ "$(SRCDIR)\wiki.wiki" OBJ = "$(OX)\add$O" \ "$(OX)\ajax$O" \ | > | 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 | "$(SRCDIR)\sounds\c.wav" \ "$(SRCDIR)\sounds\d.wav" \ "$(SRCDIR)\sounds\e.wav" \ "$(SRCDIR)\sounds\f.wav" \ "$(SRCDIR)\style.admin_log.css" \ "$(SRCDIR)\style.chat.css" \ "$(SRCDIR)\style.fileedit.css" \ "$(SRCDIR)\style.pikchrshow.css" \ "$(SRCDIR)\style.wikiedit.css" \ "$(SRCDIR)\tree.js" \ "$(SRCDIR)\useredit.js" \ "$(SRCDIR)\wiki.wiki" OBJ = "$(OX)\add$O" \ "$(OX)\ajax$O" \ |
︙ | ︙ | |||
1127 1128 1129 1130 1131 1132 1133 | "$(OBJDIR)\json_status$O" : "$(SRCDIR)\json_detail.h" "$(OBJDIR)\json_tag$O" : "$(SRCDIR)\json_detail.h" "$(OBJDIR)\json_timeline$O" : "$(SRCDIR)\json_detail.h" "$(OBJDIR)\json_user$O" : "$(SRCDIR)\json_detail.h" "$(OBJDIR)\json_wiki$O" : "$(SRCDIR)\json_detail.h" "$(OX)\builtin_data.reslist": $(EXTRA_FILES) "$(B)\win\Makefile.msc" | > > > | | 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 | "$(OBJDIR)\json_status$O" : "$(SRCDIR)\json_detail.h" "$(OBJDIR)\json_tag$O" : "$(SRCDIR)\json_detail.h" "$(OBJDIR)\json_timeline$O" : "$(SRCDIR)\json_detail.h" "$(OBJDIR)\json_user$O" : "$(SRCDIR)\json_detail.h" "$(OBJDIR)\json_wiki$O" : "$(SRCDIR)\json_detail.h" "$(OX)\builtin_data.reslist": $(EXTRA_FILES) "$(B)\win\Makefile.msc" echo "$(SRCDIR)\../extsrc/pikchr-worker.js" > $@ echo "$(SRCDIR)\../extsrc/pikchr.js" >> $@ echo "$(SRCDIR)\../extsrc/pikchr.wasm" >> $@ echo "$(SRCDIR)\../skins/ardoise/css.txt" >> $@ echo "$(SRCDIR)\../skins/ardoise/details.txt" >> $@ echo "$(SRCDIR)\../skins/ardoise/footer.txt" >> $@ echo "$(SRCDIR)\../skins/ardoise/header.txt" >> $@ echo "$(SRCDIR)\../skins/black_and_white/css.txt" >> $@ echo "$(SRCDIR)\../skins/black_and_white/details.txt" >> $@ echo "$(SRCDIR)\../skins/black_and_white/footer.txt" >> $@ echo "$(SRCDIR)\../skins/black_and_white/header.txt" >> $@ |
︙ | ︙ | |||
1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 | echo "$(SRCDIR)\fossil.fetch.js" >> $@ echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@ echo "$(SRCDIR)\fossil.page.brlist.js" >> $@ echo "$(SRCDIR)\fossil.page.chat.js" >> $@ echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@ echo "$(SRCDIR)\fossil.page.whistory.js" >> $@ echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ echo "$(SRCDIR)\fossil.pikchr.js" >> $@ echo "$(SRCDIR)\fossil.popupwidget.js" >> $@ echo "$(SRCDIR)\fossil.storage.js" >> $@ echo "$(SRCDIR)\fossil.tabs.js" >> $@ echo "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" >> $@ | > | 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 | echo "$(SRCDIR)\fossil.fetch.js" >> $@ echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@ echo "$(SRCDIR)\fossil.page.brlist.js" >> $@ echo "$(SRCDIR)\fossil.page.chat.js" >> $@ echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@ echo "$(SRCDIR)\fossil.page.pikchrshowasm.js" >> $@ echo "$(SRCDIR)\fossil.page.whistory.js" >> $@ echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ echo "$(SRCDIR)\fossil.pikchr.js" >> $@ echo "$(SRCDIR)\fossil.popupwidget.js" >> $@ echo "$(SRCDIR)\fossil.storage.js" >> $@ echo "$(SRCDIR)\fossil.tabs.js" >> $@ echo "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" >> $@ |
︙ | ︙ | |||
1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 | echo "$(SRCDIR)\sounds/c.wav" >> $@ echo "$(SRCDIR)\sounds/d.wav" >> $@ echo "$(SRCDIR)\sounds/e.wav" >> $@ echo "$(SRCDIR)\sounds/f.wav" >> $@ echo "$(SRCDIR)\style.admin_log.css" >> $@ echo "$(SRCDIR)\style.chat.css" >> $@ echo "$(SRCDIR)\style.fileedit.css" >> $@ echo "$(SRCDIR)\style.wikiedit.css" >> $@ echo "$(SRCDIR)\tree.js" >> $@ echo "$(SRCDIR)\useredit.js" >> $@ echo "$(SRCDIR)\wiki.wiki" >> $@ "$(OX)\add$O" : "$(OX)\add_.c" "$(OX)\add.h" $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\add_.c" | > | 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 | echo "$(SRCDIR)\sounds/c.wav" >> $@ echo "$(SRCDIR)\sounds/d.wav" >> $@ echo "$(SRCDIR)\sounds/e.wav" >> $@ echo "$(SRCDIR)\sounds/f.wav" >> $@ echo "$(SRCDIR)\style.admin_log.css" >> $@ echo "$(SRCDIR)\style.chat.css" >> $@ echo "$(SRCDIR)\style.fileedit.css" >> $@ echo "$(SRCDIR)\style.pikchrshow.css" >> $@ echo "$(SRCDIR)\style.wikiedit.css" >> $@ echo "$(SRCDIR)\tree.js" >> $@ echo "$(SRCDIR)\useredit.js" >> $@ echo "$(SRCDIR)\wiki.wiki" >> $@ "$(OX)\add$O" : "$(OX)\add_.c" "$(OX)\add.h" $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\add_.c" |
︙ | ︙ |
Changes to www/build.wiki.
︙ | ︙ | |||
330 331 332 333 334 335 336 | <pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre> <h2>6.0 Building on/for Android</h2> <h3>6.1 Cross-compiling from Linux</h3> | | | 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | <pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre> <h2>6.0 Building on/for Android</h2> <h3>6.1 Cross-compiling from Linux</h3> The following instructions for building Fossil for Android via Linux, without requiring a rooted OS, are adapted from [https://fossil-scm.org/forum/forumpost/e0e9de4a7e | forumpost/e0e9de4a7e]. On the development machine, from the fossil source tree: <pre><code>export CC=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang ./configure --with-openssl=none |
︙ | ︙ | |||
379 380 381 382 383 384 385 | The source of such warnings is not 100% certain. Some information about these (reportedly harmless) warnings can be found [https://stackoverflow.com/a/41900551 | on this StackOverflow post]. | < | | 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 | The source of such warnings is not 100% certain. Some information about these (reportedly harmless) warnings can be found [https://stackoverflow.com/a/41900551 | on this StackOverflow post]. <a id='fuzzer'></a> <h2>7.0 Building for Fuzz Testing</h2> This feature is primarily intended for fossil's developers and may change at any time. It is only known to work on Linux systems and has been seen to work on x86/64 and ARM. Fossil has builtin support for processing specific features using <tt>libfuzzer</tt>. The features which can be tested this way are |
︙ | ︙ | |||
458 459 460 461 462 463 464 | Flags for the fuzzer can be passed directly to fossil, e.g. <tt>-jobs=4</tt> to start four fuzzer jobs in parallel, but doing so may cause the fuzzer to <em>strip the --fuzztype flag</em>, leading to it testing the wrong thing. When passing on fuzzer-specific flags along with <tt>--fuzztype</tt>, be sure to check your system's process list to ensure that your <tt>--fuzztype</tt> flag is there. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 | Flags for the fuzzer can be passed directly to fossil, e.g. <tt>-jobs=4</tt> to start four fuzzer jobs in parallel, but doing so may cause the fuzzer to <em>strip the --fuzztype flag</em>, leading to it testing the wrong thing. When passing on fuzzer-specific flags along with <tt>--fuzztype</tt>, be sure to check your system's process list to ensure that your <tt>--fuzztype</tt> flag is there. <a id='wasm'></a> <h2>8.0 Building WebAssembly Components</h2> As of version 2.19, fossil uses one component built as [https://developer.mozilla.org/en-US/docs/WebAssembly | WebAssembly] a.k.a. WASM. Because compiling WASM code requires non-trivial client-side tooling, the repository includes compiled copies of these pieces. Most Fossil hackers should never need to concern themselves with the WASM parts, but this section describes how to for those who want or need to do so. <strong>The bits described in this section are necessary when updating <tt>extsrc/pikchr.c</tt></strong> from the upstream source, or the fossil binary will use a different version of pikchr than [/pikchrshow] does (as the latter runs the WASM build of pikchr). These instructions have only ever been tested on Linux systems. They "should" work on any Unix-like system supported by Emscripten. The fossil makefiles for Windows builds <em>do not</em> include any of the WASM-related components (patches to add that would be welcomed, of course). The first step is to configure the tree with support for [https://emscripten.org/|Emscripten]. This requires that the system has the Emscripten SDK (a.k.a. emsdk) installed, as documented at: [https://emscripten.org/docs/getting_started/downloads.html] For instructions on keeping the SDK up to date, see: [https://emscripten.org/docs/tools_reference/emsdk.html] Sidebar: getting Emscripten up and running is trivial and painless, at least on Linux systems, but the installer downloads many hundreds of megabytes of tools and dependencies, all of which will be installed under the single SDK directory (as opposed to being installed at the system level). It does, however, require that python3 be installed at the system level and it can optionally make use of a system-level cmake for certain tasks unrelated to how fossil uses the SDK. After installing the SDK, configure the fossil tree with emsdk support: <pre><code>$ ./configure --with-emsdk=/path/to/emsdk ...other options... </code></pre> If the <tt>--with-emsdk</tt> flag is not provided, the configure script will check for the environment variable <tt>EMSDK</tt>, which is one of the standard variables the SDK environment uses. If that variable is found, its value will implicitly be used in place of the missing <tt>--with-emsdk</tt> flag. Thus, if the <tt>emsdk_env.sh</tt> script is sourced into the shell before running the configure script, the SDK will be detected even without the config flag. The configure script installs some makefile variables which tell the build where to find the SDK and it generates a script named <tt>tools/emcc.sh</tt> (from the template file <tt>[/file/tools/emcc.sh.in|/tools/emcc.sh.in]</tt>), which is a wrapper around the Emscripten C compiler (<tt>emcc</tt>). The wrapper script uses the configure-time state to attempt to set up the various environment variables which are required by <tt>emcc</tt> and will fail if it cannot do so. Once it's set up the environment, it passes on all of its arguments to <tt>emcc</tt>. The WASM-related build parts are set up such that none of them should ever trigger implicity (e.g. via dependencies resolution) in a normal build cycle. They are instead explicitly built as described below. From the top of the source tree, all WASM-related components can be built with: <pre><code>$ make wasm</code></pre> As of this writing, those parts include: * <tt>extsrc/pikchr.wasm</tt> is a WASM-compiled form of <tt>extsrc/pikchr.c</tt>. * <tt>extsrc/pikchr.js</tt> is JS/WASM glue code generated by Emscripten to give JS code access to the API exported by the WASM file. Sidebar: The file <tt>[/file/extsrc/pikcher-worker.js|extsrc/pikcher-worker.js]</tt> is hand-coded and intended to be loaded as a "Worker" in JavaScript. That file loads the main module and provides an interface via which a main JavaScript thread can communicate with pikchr running in a Worker thread. The file <tt>[/file/src/fossil.page.pikchrshowasm.js|src/fossil.page.pikchrshowasm.js]</tt> implements the [/pikchrshow] app and demonstrates how <tt>pikchr-worker.js</tt> is used. When a new version of <tt>extsrc/pikchr.c</tt> is installed, the files <tt>pikchr.{js,wasm}</tt> will need to be recompiled to account for that. Running <tt>make wasm</tt> will, if the build is set up for the emsdk, recompile those: <pre><code>$ make wasm ./tools/emcc.sh -o extsrc/pikchr.js ... $ ls -la extsrc/pikchr.{js,wasm} -rw-rw-r-- 1 stephan stephan 17263 Jun 8 03:59 extsrc/pikchr.js -rw-rw-r-- 1 stephan stephan 97578 Jun 8 03:59 extsrc/pikchr.wasm </code></pre> Then run the normal build so that those files can be compiled in to the fossil binary, accessible via the [/help?cmd=/builtin|/builtin page]: <pre><code>$ make</code></pre> Before checking in those newly-built files, they need to be tested by running the [/pikchrshow] page. If that page loads, the compilation process fundamentally worked (a load failure will be made obvious to the viewer). If it fails to load then the browser's dev tools console likely provides at least a small hint (and <em>sometimes</em> a useful hint) about the nature of the problem. Don't check those files in until [/pikchrshow] runs, though! Should pikchr's C interface ever changes, <tt>pikchr-worker.js</tt> will need to be updated to accommodate it, but such modification is typically trivial. |
Changes to www/changes.wiki.
︙ | ︙ | |||
23 24 25 26 27 28 29 | * Add the new special name "start:BRANCH" to refer to the first check-in of the branch. * Support [/wiki?name=branch/generated-tkt-mimetype&p|generated "mimetype"] columns in the <var>TICKET</var> and <var>TICKETCHNG</var> tables. * Fix [/timeline?r=fix_remote_url_overwrite_with_proxy|remote-url-overwrite] bug where remote-url is overwritten by the proxy setting during sync operation. Also require explicit "system" proxy setting to use | | > > > | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | * Add the new special name "start:BRANCH" to refer to the first check-in of the branch. * Support [/wiki?name=branch/generated-tkt-mimetype&p|generated "mimetype"] columns in the <var>TICKET</var> and <var>TICKETCHNG</var> tables. * Fix [/timeline?r=fix_remote_url_overwrite_with_proxy|remote-url-overwrite] bug where remote-url is overwritten by the proxy setting during sync operation. Also require explicit "system" proxy setting to use "http_proxy" environment variable. * Reimplemented the [/pikchrshow] app to use a WebAssembly build of pikchr so that it can render pikchrs on the client instead of requiring a server round-trip. <h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2> * Added support for [./ssl-server.md|SSL/TLS server mode] for commands like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]" * The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for [/help?cmd=merge|merge --cherrypick]. * Add new setting "[/help?cmd=large-file-size|large-file-size]". If the size |
︙ | ︙ |