Fossil

Check-in [d5e4d7a9a9]
Login

Check-in [d5e4d7a9a9]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Can now create new pages without leaving the editor. Numerous layout tweaks. Improved the help tab.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | ajax-wiki-editor
Files: files | file ages | folders
SHA3-256: d5e4d7a9a92ff0f482a42f35817caaec7fc088f73bbf5c64725dcea04dc25d39
User & Date: stephan 2020-07-31 03:50:24.960
Context
2020-07-31
04:33
Moved Save button to the Preview tab, as that follows the common app-wide pattern of not enabling a save until a preview has first been triggered. ... (check-in: 31296ec204 user: stephan tags: ajax-wiki-editor)
03:50
Can now create new pages without leaving the editor. Numerous layout tweaks. Improved the help tab. ... (check-in: d5e4d7a9a9 user: stephan tags: ajax-wiki-editor)
02:02
Renamed the fileedit/wikiedit stash index keys to avoid breakage if they edit a file/page named 'index' (this unfortunately invalidates any local-storage edits in /fileedit). Minor wiki page selection list style tweak. ... (check-in: 8975f7247a user: stephan tags: ajax-wiki-editor)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
  flex-direction: row;
  flex: 1 10 auto;
  align-self: stretch;
  flex-wrap: wrap;
}
.tab-container > .tab-bar > .tab-button {
  display: inline-block;
  border-radius: 0.5em 0.5em 0 0;
  margin: 0 0.1em;
  padding: 0.25em 0.75em;
  align-self: baseline;
  border-color: inherit;
  border-width: 1px;
  border-bottom: none;
  border-top-style: inset;







|







953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
  flex-direction: row;
  flex: 1 10 auto;
  align-self: stretch;
  flex-wrap: wrap;
}
.tab-container > .tab-bar > .tab-button {
  display: inline-block;
  border-radius: 0.25em 0.25em 0 0;
  margin: 0 0.1em;
  padding: 0.25em 0.75em;
  align-self: baseline;
  border-color: inherit;
  border-width: 1px;
  border-bottom: none;
  border-top-style: inset;
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
   contain both a LABEL tag and an INPUT or SELECT control.
   The wrapper is "necessary", as opposed to placing the INPUT
   in the LABEL, so that we can include multiple INPUT
   elements (e.g. a set of radio buttons).
*/
.input-with-label {
  border: 1px inset #808080;
  border-radius: 0.5em;
  padding: 0.25em 0.4em;
  margin: 0 0.5em;
  display: inline-block;
  cursor: default;
}
.input-with-label > * {
  vertical-align: middle;







|







1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
   contain both a LABEL tag and an INPUT or SELECT control.
   The wrapper is "necessary", as opposed to placing the INPUT
   in the LABEL, so that we can include multiple INPUT
   elements (e.g. a set of radio buttons).
*/
.input-with-label {
  border: 1px inset #808080;
  border-radius: 0.25em;
  padding: 0.25em 0.4em;
  margin: 0 0.5em;
  display: inline-block;
  cursor: default;
}
.input-with-label > * {
  vertical-align: middle;
Changes to src/fossil.page.wikiedit.js.
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
    }
    if(forceEvent){
      // Force UI update
      s.dispatchEvent(new Event('change',{target:s}));
    }
  };




  const WikiList = {
    e: {
      filterCheckboxes: {
        /*map of wiki page type to checkbox for list filtering purposes,
          except for "sandbox" type, which is assumed to be covered by
          the "normal" type filter. */}









    },
    /** Updates OPTION elements to reflect whether the page has
        local changes or is new/unsaved. */
    refreshStashMarks: function(){

      const sel = this.e.select;
      Object.keys(sel.options).forEach(function(key){
        const opt = sel.options[key];
        const stashed = $stash.getWinfo({name:opt.value});
        if(stashed){
          const isNew = 'sandbox'===stashed.type ? false : !stashed.version;
          D.addClass(opt, isNew ? 'stashed-new' :'stashed');
        }else{
          D.removeClass(opt, 'stashed', 'stashed-new');
        }

      });
    },
    /** Removes the given wiki page entry from the page selection
        list, if it's in the list. */
    removeEntry: function(name){
      const sel = this.e.select;
      var ndx = sel.selectedIndex;
      sel.value = name;
      if(sel.selectedIndex>-1){
        if(ndx === sel.selectedIndex) ndx = -1;
        sel.options.remove(sel.selectedIndex);
      }
      sel.selectedIndex = ndx;
    },


    /** Loads the page list and populates the selection list. */

    loadList: function callee(){




      delete this.pageMap;

      if(!callee.sorticase){
        callee.sorticase = function(l,r){

          l = l.toLowerCase();
          r = r.toLowerCase();
          return l<=r ? -1 : 1;
        };
        const self = this;
        callee.onload = function(list){
          /* Jump through some hoops to integrate new/unsaved
             pages into the list of existing pages... We use a map
             as an intermediary in order to filter out any local-stash
             dupes from server-side copies. */

          const map = {}, ndx = $stash.getIndex(), sel = self.e.select;
          D.clearElement(sel);
          list.forEach((winfo)=>map[winfo.name] = winfo);
          Object.keys(ndx).forEach(function(key){
            const winfo = ndx[key];
            if(!winfo.version/*new page*/) map[winfo.name] = winfo;
          });

          Object.keys(map)
            .sort(callee.sorticase)
            .forEach(function(name){
              const winfo = map[name];
              const opt = D.option(sel, winfo.name);
              const wtype = opt.dataset.wtype =
                    winfo.type==='sandbox' ? 'normal' : winfo.type;
              const cb = self.e.filterCheckboxes[wtype];
              if(cb && !cb.checked) D.addClass(opt, 'hidden');
            });
          D.enable(sel);
          if(P.winfo) sel.value = P.winfo.name;
          self.refreshStashMarks();










          F.message("Loaded page list.");
        };
      }
      F.fetch('wikiajax/list',{
        urlParams:{verbose:true},
        responseType: 'json',
        onload: callee.onload
      });
      return this;
    },

    /**
























































       Installs a wiki page selection list into the given parent DOM
       element and loads the page list from the server.
    */
    init: function(parentElem){
      const sel = D.select(), btn = D.button("Reload page list");
      this.e.select = sel;
      D.addClass(parentElem, 'wikiedit-page-list-wrapper');
      D.clearElement(parentElem);
      D.append(
        parentElem,
        D.append(D.fieldset("Select a page to edit"),
                 sel)
      );
      D.attr(sel, 'size', 15);







>
>
>





|
>
>
>
>
>
>
>
>
>




>
|
|
|







>















>
|
>
|
>
>
>
>
|
>


>




<
<
<
<
<
<
>
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>










>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




|

|







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
    }
    if(forceEvent){
      // Force UI update
      s.dispatchEvent(new Event('change',{target:s}));
    }
  };

  /**
     Sets up and maintains the widgets for the list of wiki pages.
  */
  const WikiList = {
    e: {
      filterCheckboxes: {
        /*map of wiki page type to checkbox for list filtering purposes,
          except for "sandbox" type, which is assumed to be covered by
          the "normal" type filter. */},
    },
    cache: {
      names: {
        /* Map of page names to "something." We don't map to their
           winfo bits because those regularly get swapped out via
           de/serialization. We need this map to support the add-new-page
           feature, to give us a way to check for dupes without asking
           the server or walking through the whole selection list.
        */}
    },
    /** Updates OPTION elements to reflect whether the page has
        local changes or is new/unsaved. */
    refreshStashMarks: function(){
      this.cache.names = {/*must reset it to acount for local page removals*/};
      const select = this.e.select, self = this;
      Object.keys(select.options).forEach(function(key){
        const opt = select.options[key];
        const stashed = $stash.getWinfo({name:opt.value});
        if(stashed){
          const isNew = 'sandbox'===stashed.type ? false : !stashed.version;
          D.addClass(opt, isNew ? 'stashed-new' :'stashed');
        }else{
          D.removeClass(opt, 'stashed', 'stashed-new');
        }
        self.cache.names[opt.value] = true;
      });
    },
    /** Removes the given wiki page entry from the page selection
        list, if it's in the list. */
    removeEntry: function(name){
      const sel = this.e.select;
      var ndx = sel.selectedIndex;
      sel.value = name;
      if(sel.selectedIndex>-1){
        if(ndx === sel.selectedIndex) ndx = -1;
        sel.options.remove(sel.selectedIndex);
      }
      sel.selectedIndex = ndx;
    },

    /**
       Rebuilds the selection list. Necessary when it's loaded from
       the server or we locally create a new page. */
    _rebuildList: function callee(){
      /* Jump through some hoops to integrate new/unsaved
         pages into the list of existing pages... We use a map
         as an intermediary in order to filter out any local-stash
         dupes from server-side copies. */
      const list = this.cache.pageList;
      if(!list) return;
      if(!callee.sorticase){
        callee.sorticase = function(l,r){
          if(l===r) return 0;
          l = l.toLowerCase();
          r = r.toLowerCase();
          return l<=r ? -1 : 1;
        };






      }
      const map = {}, ndx = $stash.getIndex(), sel = this.e.select;
      D.clearElement(sel);
      list.forEach((winfo)=>map[winfo.name] = winfo);
      Object.keys(ndx).forEach(function(key){
        const winfo = ndx[key];
        if(!winfo.version/*new page*/) map[winfo.name] = winfo;
      });
      const self = this;
      Object.keys(map)
        .sort(callee.sorticase)
        .forEach(function(name){
          const winfo = map[name];
          const opt = D.option(sel, winfo.name);
          const wtype = opt.dataset.wtype =
                winfo.type==='sandbox' ? 'normal' : (winfo.type||'normal');
          const cb = self.e.filterCheckboxes[wtype];
          if(cb && !cb.checked) D.addClass(opt, 'hidden');
        });
      D.enable(sel);
      if(P.winfo) sel.value = P.winfo.name;
      this.refreshStashMarks();
    },
    
    /** Loads the page list and populates the selection list. */
    loadList: function callee(){
      delete this.pageMap;
      if(!callee.onload){
        const self = this;
        callee.onload = function(list){
          self.cache.pageList = list;
          self._rebuildList();
          F.message("Loaded page list.");
        };
      }
      F.fetch('wikiajax/list',{
        urlParams:{verbose:true},
        responseType: 'json',
        onload: callee.onload
      });
      return this;
    },

    /**
       Returns true if the given name appears to be a valid
       wiki page name, noting that the final arbitrator is the
       server. On validation error it emits a message via fossil.error()
       and returns false.
    */
    validatePageName: function(name){
      var err;
      if(!name){
        err = "may not be empty";
      }else if(this.cache.names.hasOwnProperty(name)){
        err = "page already exists: "+name;
      }else if(name.length>100){
        err = "too long (limit is 100)";
      }else if(/\s{2,}/.test(name)){
        err = "multiple consecutive spaces";
      }else if(/[\t\r\n]/.test(name)){
        err = "contains control character(s)";
      }else{
        let i = 0, n = name.length, c;
        for( ; i < n; ++i ){
          if(name.charCodeAt(i)<0x20){
            err = "contains control character(s)";
            break;
          }
        }
      }
      if(err){
        F.error("Invalid name:",err);
      }
      return !err;
    },

    /**
       If the given name is valid, a new page with that (trimmed) name
       is added to the local stash.
    */
    addNewPage: function(name){
      name = name.trim();
      if(!this.validatePageName(name)) return false;
      var wtype = 'normal';
      if(0===name.indexOf('checkin/')) wtype = 'checkin';
      else if(0===name.indexOf('branch/')) wtype = 'branch';
      else if(0===name.indexOf('tag/')) wtype = 'tag';
      /* ^^^ note that we're not validating that, e.g., checkin/XYZ
         has a full artifact ID after "checkin/". */
      const winfo = {
        name: name, type: wtype, mimetype: 'text/x-fossil-wiki',
        version: null, parent: null
      };
      $stash.updateWinfo(winfo, '');
      this._rebuildList();
      P.loadPage(winfo.name);
      return true;
    },
    
    /**
       Installs a wiki page selection list into the given parent DOM
       element and loads the page list from the server.
    */
    init: function(parentElem){
      const sel = D.select(), btn = D.addClass(D.button("Reload page list"), 'save');
      this.e.select = sel;
      D.addClass(parentElem, 'WikiList');
      D.clearElement(parentElem);
      D.append(
        parentElem,
        D.append(D.fieldset("Select a page to edit"),
                 sel)
      );
      D.attr(sel, 'size', 15);
409
410
411
412
413
414
415




















416
417
418
419
420

421
422
423
424
425
426
427
      D.append(
        fsLegendBody,
        D.append(D.span(), P.config.editStateMarkers.isModified,
                 " = page has local edits"),
        D.append(D.span(), P.config.editStateMarkers.isNew,
                 " = page is new/unsaved")
      );




















      D.append(
        parentElem,
        D.append(D.addClass(D.div(), 'fieldset-wrapper'),
                 fsFilter, fsLegend)
      );

      D.append(parentElem, btn);
      btn.addEventListener('click', ()=>this.loadList(), false);
      this.loadList();
      sel.addEventListener('change', (e)=>P.loadPage(e.target.value), false);
      F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks(), false);
      delete this.init;
    }







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



|

>







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
      D.append(
        fsLegendBody,
        D.append(D.span(), P.config.editStateMarkers.isModified,
                 " = page has local edits"),
        D.append(D.span(), P.config.editStateMarkers.isNew,
                 " = page is new/unsaved")
      );

      const fsNewPage = D.fieldset("Create new page"),
            fsNewPageBody = D.div(),
            newPageName = D.input('text'),
            newPageBtn = D.button("Add page locally")
            ;
      D.append(parentElem, fsNewPage);
      D.append(fsNewPage, fsNewPageBody);
      D.addClass(fsNewPageBody, 'flex-container', 'flex-column', 'new-page');
      D.append(
        fsNewPageBody, newPageName, newPageBtn,
        D.append(D.addClass(D.span(), 'mini-tip'),
                 "New pages exist only in this browser until they are saved.")
      );
      newPageBtn.addEventListener('click', function(){
        if(self.addNewPage(newPageName.value)){
          newPageName.value = '';
        }
      }, false);

      D.append(
        parentElem,
        D.append(D.addClass(D.div(), 'fieldset-wrapper'),
                 fsFilter, fsNewPage, fsLegend)
      );
      
      D.append(parentElem, btn);
      btn.addEventListener('click', ()=>this.loadList(), false);
      this.loadList();
      sel.addEventListener('change', (e)=>P.loadPage(e.target.value), false);
      F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks(), false);
      delete this.init;
    }
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
      /* Move the status bar between the tab buttons and
         tab panels. Seems to be the best fit in terms of
         functionality and visibility. */
      E('#fossil-status-bar'), P.tabs.e.tabs
    );

    P.tabs.addEventListener(
      /* Set up auto-refresh of the preview tab... */
      'before-switch-to', function(ev){
        if(ev.detail===P.e.tabs.preview){
          P.baseHrefForWiki();
          if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
        }else if(ev.detail===P.e.tabs.diff){
          /* Work around a weird bug where the page gets wider than
             the window when the diff tab is NOT in view and the
             current SBS diff widget is wider than the window. When
             the diff IS in view then CSS overflow magically reduces
             the page size again. Weird. Maybe FF-specific. Note that
             this weirdness happens even though P.e.diffTarget's parent
             is hidden (and therefore P.e.diffTarget is also hidden).
          */
          D.removeClass(P.e.diffTarget, 'hidden');
        }else if(ev.detail===P.e.tabs.save){
          const btn = P.e.btnSave;

          if(!P.winfo || !P.getStashedWinfo(P.winfo)){
            D.disable(btn).innerText =
              "There are no changes to save";
          }else{
            D.enable(btn).innerText = "Save changes";
          }
        }







|
















>







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
      /* Move the status bar between the tab buttons and
         tab panels. Seems to be the best fit in terms of
         functionality and visibility. */
      E('#fossil-status-bar'), P.tabs.e.tabs
    );

    P.tabs.addEventListener(
      /* Set up some before-switch-to tab event tasks... */
      'before-switch-to', function(ev){
        if(ev.detail===P.e.tabs.preview){
          P.baseHrefForWiki();
          if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
        }else if(ev.detail===P.e.tabs.diff){
          /* Work around a weird bug where the page gets wider than
             the window when the diff tab is NOT in view and the
             current SBS diff widget is wider than the window. When
             the diff IS in view then CSS overflow magically reduces
             the page size again. Weird. Maybe FF-specific. Note that
             this weirdness happens even though P.e.diffTarget's parent
             is hidden (and therefore P.e.diffTarget is also hidden).
          */
          D.removeClass(P.e.diffTarget, 'hidden');
        }else if(ev.detail===P.e.tabs.save){
          const btn = P.e.btnSave;
          P.updateAttachmentView();
          if(!P.winfo || !P.getStashedWinfo(P.winfo)){
            D.disable(btn).innerText =
              "There are no changes to save";
          }else{
            D.enable(btn).innerText = "Save changes";
          }
        }
575
576
577
578
579
580
581
582
583
584

585
586
587
588
589
590
591
                  "then use the Discard button.");
          return;
        }
        P.unstashContent()
        if(w.version || w.type==='sandbox'){
          P.loadPage();
        }else{
          delete P.winfo;
          WikiList.removeEntry(w.name);
          P.updatePageTitle();

          F.message("Discarded new page ["+w.name+"].");
        }
      },
      ticks: 3
    });
    F.confirmer(P.e.btnSave, {
      confirmText: "Really save changes?",







<


>







682
683
684
685
686
687
688

689
690
691
692
693
694
695
696
697
698
                  "then use the Discard button.");
          return;
        }
        P.unstashContent()
        if(w.version || w.type==='sandbox'){
          P.loadPage();
        }else{

          WikiList.removeEntry(w.name);
          P.updatePageTitle();
          delete P.winfo;
          F.message("Discarded new page ["+w.name+"].");
        }
      },
      ticks: 3
    });
    F.confirmer(P.e.btnSave, {
      confirmText: "Really save changes?",
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
        P.e.selectMimetype.value = winfo.mimetype;
        P.tabs.switchToTab(P.e.tabs.content);
        P.wikiContent(winfo.content || '');
        WikiList.e.select.value = winfo.name;
        if(!winfo.version && winfo.type!=='sandbox'){
          F.error('You are editing a new, unsaved page:',winfo.name);
        }
        P.updateAttachmentView().updatePageTitle();
      },
      false
    );
    P.updateAttachmentView();
  }/*F.onPageLoad()*/);

  /**
     Returns true if fossil.page.winfo is set, indicating that a page
     has been loaded, else it reports an error and returns false.

     If passed a truthy value any error message about not having







|



|







767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
        P.e.selectMimetype.value = winfo.mimetype;
        P.tabs.switchToTab(P.e.tabs.content);
        P.wikiContent(winfo.content || '');
        WikiList.e.select.value = winfo.name;
        if(!winfo.version && winfo.type!=='sandbox'){
          F.error('You are editing a new, unsaved page:',winfo.name);
        }
        P.updatePageTitle();
      },
      false
    );
    P.updatePageTitle().updateAttachmentView();
  }/*F.onPageLoad()*/);

  /**
     Returns true if fossil.page.winfo is set, indicating that a page
     has been loaded, else it reports an error and returns false.

     If passed a truthy value any error message about not having
709
710
711
712
713
714
715
716

717
718

719
720
721
722
723
724
725
726
  /** Updates attachment-related links and returns this. */
  P.updateAttachmentView = function(){
    const wrapper = P.e.attachmentWrapper;
    D.clearElement(wrapper);
    const ul = D.ul();
    D.append(wrapper, ul);
    if(!P.winfo){
      D.append(D.li(ul), "No page loaded.");

      return this;
    }else if(!P.winfo.version){

      D.append(D.li(ul), "A new/unsaved page cannot have attachments.");
      return this;
    }
    const wi = P.winfo;
    D.append(
      D.li(ul),
      D.a(F.repoUrl('attachadd',{
        page:wi.name,







|
>


>
|







816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
  /** Updates attachment-related links and returns this. */
  P.updateAttachmentView = function(){
    const wrapper = P.e.attachmentWrapper;
    D.clearElement(wrapper);
    const ul = D.ul();
    D.append(wrapper, ul);
    if(!P.winfo){
      D.append(D.li(ul),
               "Load a page to get access to its attachment-related pages.");
      return this;
    }else if(!P.winfo.version){
      D.append(D.li(ul),
               "A new/unsaved page cannot have attachments. Save it first.");
      return this;
    }
    const wi = P.winfo;
    D.append(
      D.li(ul),
      D.a(F.repoUrl('attachadd',{
        page:wi.name,
Changes to src/style.wikiedit.css.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
body.wikieedit.waiting * {
  /* Triggered during AJAX requests. */
  cursor: wait;
}
body.wikiedit textarea,
body.wikiedit textarea:focus,
body.wikiedit input,
body.wikiedit input:focus{
  /* The sudden appearance of a border (as in the Ardoise skin)
     shifts the layout in unsightly ways */
  border: reset;
}
body.wikiedit div.wikiedit-preview {
  margin: 0;
  padding: 0;
}
body.wikiedit #wikiedit-tabs {
  margin: 1em 0 0 0;










|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
body.wikieedit.waiting * {
  /* Triggered during AJAX requests. */
  cursor: wait;
}
body.wikiedit textarea,
body.wikiedit textarea:focus,
body.wikiedit input,
body.wikiedit input:focus{
  /* The sudden appearance of a border (as in the Ardoise skin)
     shifts the layout in unsightly ways */
  border: revert;
}
body.wikiedit div.wikiedit-preview {
  margin: 0;
  padding: 0;
}
body.wikiedit #wikiedit-tabs {
  margin: 1em 0 0 0;
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















}
body.wikiedit .wikiedit-options > div > * {
  margin: 0.25em;
}
body.wikiedit .wikiedit-options.flex-container.flex-row {
  align-items: first baseline;
}
body.wikiedit .wikiedit-page-list-wrapper {
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  justify-content: center;
  align-items: start;
}
body.wikiedit .wikiedit-page-list-wrapper select option {



  margin: 0.5em 0;
}
body.wikiedit .wikiedit-page-list-wrapper select option.stashed::before {
/* Maintenance reminder: the option.stashed/stashed-new "content" values
   are duplicated in fossil.page.wikiedit.js and need to be changed there
   if they are changed here: see fossil.page.config.editStateMarkers */
  content: "[*] ";
}
body.wikiedit .wikiedit-page-list-wrapper select option.stashed-new::before {
  content: "[+] ";
}
body.wikiedit textarea {
  max-width: calc(100% - 1em);
}
body.wikiedit .tabs .tab-panel {
  /* Needed for wide diffs */
  overflow: auto;
}
body.wikiedit .wikiedit-page-list-wrapper fieldset {
  padding: 0.25em;
}



body.wikiedit .wikiedit-page-list-wrapper fieldset > :not(legend) {
  /* Stretch page selection list when it's empty or only has short page names */
  width: 100%;
}
body.wikiedit .wikiedit-page-list-wrapper .fieldset-wrapper {
  /* Container for the filter and edit status fieldsets */
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
  justify-content: stretch;
  margin: 0 0 1em 0;
}






















|


<
<


|
>
>
>


|





|









|


>
>
>
|



|






|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
}
body.wikiedit .wikiedit-options > div > * {
  margin: 0.25em;
}
body.wikiedit .wikiedit-options.flex-container.flex-row {
  align-items: first baseline;
}
body.wikiedit .WikiList {
  display: flex;
  flex-direction: column;


  align-items: start;
}
body.wikiedit .WikiList select {
  font-size: 110%;
}
body.wikiedit .WikiList select option {
  margin: 0.5em 0;
}
body.wikiedit .WikiList select option.stashed::before {
/* Maintenance reminder: the option.stashed/stashed-new "content" values
   are duplicated in fossil.page.wikiedit.js and need to be changed there
   if they are changed here: see fossil.page.config.editStateMarkers */
  content: "[*] ";
}
body.wikiedit .WikiList select option.stashed-new::before {
  content: "[+] ";
}
body.wikiedit textarea {
  max-width: calc(100% - 1em);
}
body.wikiedit .tabs .tab-panel {
  /* Needed for wide diffs */
  overflow: auto;
}
body.wikiedit .WikiList fieldset {
  padding: 0.25em;
}
body.wikiedit .WikiList legend {
  font-size: 90%;
}
body.wikiedit .WikiList fieldset > :not(legend) {
  /* Stretch page selection list when it's empty or only has short page names */
  width: 100%;
}
body.wikiedit .WikiList .fieldset-wrapper {
  /* Container for the filter and edit status fieldsets */
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
  justify-content: stretch;
  margin: 0;
}
body.wikiedit .WikiList button.save {
  margin: 1em 0 0 0;
}
body.wikiedit .WikiList .new-page {
  align-items: flex-start;
  max-width: 15em;
}
body.wikiedit .WikiList .new-page input {
}
body.wikiedit #wikiedit-tab-save h3 {
  margin: 0;
}
body.wikiedit span.mini-tip {
  font-size: 80%;
}
Changes to src/wiki.c.
1129
1130
1131
1132
1133
1134
1135


1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149






1150
1151
1152
1153
1154
1155
1156
  /******* Content tab *******/
  {
    CX("<div id='wikiedit-tab-content' "
       "data-tab-parent='wikiedit-tabs' "
       "data-tab-label='Wiki Editor'"
       ">");
    CX("<div class='flex-container flex-row child-gap-small'>");


    mimetype_option_menu(0);
    CX("<button class='wikiedit-content-reload' "
       "title='Reload the file from the server, discarding "
       "any local edits. To help avoid accidental loss of "
       "edits, it requires confirmation (a second click) within "
       "a few seconds or it will not reload.'"
       ">Discard &amp; Reload</button>");
    style_select_list_int("select-font-size",
                          "editor_font_size", "Editor font size",
                          NULL/*tooltip*/,
                          100,
                          "100%", 100, "125%", 125,
                          "150%", 150, "175%", 175,
                          "200%", 200, NULL);






    CX("</div>");
    CX("<div class='flex-container flex-column stretch'>");
    CX("<textarea name='content' id='wikiedit-content-editor' "
       "class='wikiedit' "
       "rows='25' cols='80'>");
    CX("</textarea>");
    CX("</div>"/*textarea wrapper*/);







>
>

<
<
<
<
<
|







>
>
>
>
>
>







1129
1130
1131
1132
1133
1134
1135
1136
1137
1138





1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
  /******* Content tab *******/
  {
    CX("<div id='wikiedit-tab-content' "
       "data-tab-parent='wikiedit-tabs' "
       "data-tab-label='Wiki Editor'"
       ">");
    CX("<div class='flex-container flex-row child-gap-small'>");
    CX("<span class='input-with-label'>"
       "<label>Mime type</label>");
    mimetype_option_menu(0);





    CX("</span>");
    style_select_list_int("select-font-size",
                          "editor_font_size", "Editor font size",
                          NULL/*tooltip*/,
                          100,
                          "100%", 100, "125%", 125,
                          "150%", 150, "175%", 175,
                          "200%", 200, NULL);
    CX("<button class='wikiedit-content-reload' "
       "title='Reload the file from the server, discarding "
       "any local edits. To help avoid accidental loss of "
       "edits, it requires confirmation (a second click) within "
       "a few seconds or it will not reload.'"
       ">Discard &amp; Reload</button>");
    CX("</div>");
    CX("<div class='flex-container flex-column stretch'>");
    CX("<textarea name='content' id='wikiedit-content-editor' "
       "class='wikiedit' "
       "rows='25' cols='80'>");
    CX("</textarea>");
    CX("</div>"/*textarea wrapper*/);
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231

1232
1233
1234
1235








1236
1237
1238
1239
1240
1241
1242
1243
       "data-tab-label='Diff'"
       ">");

    CX("<div class='wikiedit-options flex-container flex-row' "
       "id='wikiedit-tab-diff-buttons'>");
    CX("<button class='sbs'>Side-by-side</button>"
       "<button class='unified'>Unified</button>");
    if(0){
      /* For the time being let's just ignore all whitespace
      ** changes, as files with Windows-style EOLs always show
      ** more diffs than we want then they're submitted to
      ** ?ajax=diff because JS normalizes them to Unix EOLs.
      ** We can revisit this decision later. */
      style_select_list_int("diff-ws-policy",
                            "diff_ws", "Whitespace",
                            "Whitespace handling policy.",
                            2,
                            "Diff all whitespace", 0,
                            "Ignore EOL whitespace", 1,
                            "Ignore all whitespace", 2,
                            NULL);
    }
    CX("</div>");
    CX("<div id='wikiedit-tab-diff-wrapper'>"
       "Diffs will be shown here."
       "</div>");
    CX("</div>"/*#wikiedit-tab-diff*/);
  }

  {
    CX("<div id='wikiedit-tab-save' "
       "data-tab-parent='wikiedit-tabs' "
       "data-tab-label='Save, etc.'"
       ">");
    CX("<button class='wikiedit-save'>Save</button>");
    CX("<hr>");
    CX("Wiki formatting rules:");
    CX("<ul>");
    CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>");
    CX("<li><a href='%R/md_rules'>Markdown format</a></li>");

    CX("</ul>");
    CX("<hr>Attachments:");
    CX("<div id='wikiedit-attachments'></div>"
       /* Filled out by JS */);








    CX("</div>");
  }
  
  style_emit_script_fossil_bootstrap(0);
  append_diff_javascript(1);
  style_emit_script_fetch(0);
  style_emit_script_tabs(0)/*also emits fossil.dom*/;
  style_emit_script_confirmer(0);







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










|



|



>

|


>
>
>
>
>
>
>
>
|







1195
1196
1197
1198
1199
1200
1201















1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
       "data-tab-label='Diff'"
       ">");

    CX("<div class='wikiedit-options flex-container flex-row' "
       "id='wikiedit-tab-diff-buttons'>");
    CX("<button class='sbs'>Side-by-side</button>"
       "<button class='unified'>Unified</button>");















    CX("</div>");
    CX("<div id='wikiedit-tab-diff-wrapper'>"
       "Diffs will be shown here."
       "</div>");
    CX("</div>"/*#wikiedit-tab-diff*/);
  }

  {
    CX("<div id='wikiedit-tab-save' "
       "data-tab-parent='wikiedit-tabs' "
       "data-tab-label='Save, Help, etc.'"
       ">");
    CX("<button class='wikiedit-save'>Save</button>");
    CX("<hr>");
    CX("<h3>Wiki formatting rules</h3>");
    CX("<ul>");
    CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>");
    CX("<li><a href='%R/md_rules'>Markdown format</a></li>");
    CX("<li>Plain-text pages use no special formatting.</li>");
    CX("</ul>");
    CX("<hr><h3>Attachments</h3>");
    CX("<div id='wikiedit-attachments'></div>"
       /* Filled out by JS */);
    CX("<hr><h3>The \"Sandbox\" Page</h3>");
    CX("<p>The page named \"sandbox\" is not a real wiki page. "
       "It provides a place where users may test out wiki syntax "
       "without having to actually save anything, nor pollute "
       "the repo with endless test runs. Any attempt to save the "
       "sandbox page will fail.</p>");
    CX("<hr><h3>Wiki Name Rules</h3>");
    well_formed_wiki_name_rules();
    CX("</div>"/*#wikiedit-tab-save*/);
  }
  
  style_emit_script_fossil_bootstrap(0);
  append_diff_javascript(1);
  style_emit_script_fetch(0);
  style_emit_script_tabs(0)/*also emits fossil.dom*/;
  style_emit_script_confirmer(0);