Fossil SCM

Can now create new pages without leaving the editor. Numerous layout tweaks. Improved the help tab.

stephan 2020-07-31 03:50 ajax-wiki-editor
Commit d5e4d7a9a92ff0f482a42f35817caaec7fc088f73bbf5c64725dcea04dc25d39
+2 -2
--- src/default.css
+++ src/default.css
@@ -955,11 +955,11 @@
955955
align-self: stretch;
956956
flex-wrap: wrap;
957957
}
958958
.tab-container > .tab-bar > .tab-button {
959959
display: inline-block;
960
- border-radius: 0.5em 0.5em 0 0;
960
+ border-radius: 0.25em 0.25em 0 0;
961961
margin: 0 0.1em;
962962
padding: 0.25em 0.75em;
963963
align-self: baseline;
964964
border-color: inherit;
965965
border-width: 1px;
@@ -1054,11 +1054,11 @@
10541054
in the LABEL, so that we can include multiple INPUT
10551055
elements (e.g. a set of radio buttons).
10561056
*/
10571057
.input-with-label {
10581058
border: 1px inset #808080;
1059
- border-radius: 0.5em;
1059
+ border-radius: 0.25em;
10601060
padding: 0.25em 0.4em;
10611061
margin: 0 0.5em;
10621062
display: inline-block;
10631063
cursor: default;
10641064
}
10651065
--- src/default.css
+++ src/default.css
@@ -955,11 +955,11 @@
955 align-self: stretch;
956 flex-wrap: wrap;
957 }
958 .tab-container > .tab-bar > .tab-button {
959 display: inline-block;
960 border-radius: 0.5em 0.5em 0 0;
961 margin: 0 0.1em;
962 padding: 0.25em 0.75em;
963 align-self: baseline;
964 border-color: inherit;
965 border-width: 1px;
@@ -1054,11 +1054,11 @@
1054 in the LABEL, so that we can include multiple INPUT
1055 elements (e.g. a set of radio buttons).
1056 */
1057 .input-with-label {
1058 border: 1px inset #808080;
1059 border-radius: 0.5em;
1060 padding: 0.25em 0.4em;
1061 margin: 0 0.5em;
1062 display: inline-block;
1063 cursor: default;
1064 }
1065
--- src/default.css
+++ src/default.css
@@ -955,11 +955,11 @@
955 align-self: stretch;
956 flex-wrap: wrap;
957 }
958 .tab-container > .tab-bar > .tab-button {
959 display: inline-block;
960 border-radius: 0.25em 0.25em 0 0;
961 margin: 0 0.1em;
962 padding: 0.25em 0.75em;
963 align-self: baseline;
964 border-color: inherit;
965 border-width: 1px;
@@ -1054,11 +1054,11 @@
1054 in the LABEL, so that we can include multiple INPUT
1055 elements (e.g. a set of radio buttons).
1056 */
1057 .input-with-label {
1058 border: 1px inset #808080;
1059 border-radius: 0.25em;
1060 padding: 0.25em 0.4em;
1061 margin: 0 0.5em;
1062 display: inline-block;
1063 cursor: default;
1064 }
1065
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -269,30 +269,44 @@
269269
// Force UI update
270270
s.dispatchEvent(new Event('change',{target:s}));
271271
}
272272
};
273273
274
+ /**
275
+ Sets up and maintains the widgets for the list of wiki pages.
276
+ */
274277
const WikiList = {
275278
e: {
276279
filterCheckboxes: {
277280
/*map of wiki page type to checkbox for list filtering purposes,
278281
except for "sandbox" type, which is assumed to be covered by
279
- the "normal" type filter. */}
282
+ the "normal" type filter. */},
283
+ },
284
+ cache: {
285
+ names: {
286
+ /* Map of page names to "something." We don't map to their
287
+ winfo bits because those regularly get swapped out via
288
+ de/serialization. We need this map to support the add-new-page
289
+ feature, to give us a way to check for dupes without asking
290
+ the server or walking through the whole selection list.
291
+ */}
280292
},
281293
/** Updates OPTION elements to reflect whether the page has
282294
local changes or is new/unsaved. */
283295
refreshStashMarks: function(){
284
- const sel = this.e.select;
285
- Object.keys(sel.options).forEach(function(key){
286
- const opt = sel.options[key];
296
+ this.cache.names = {/*must reset it to acount for local page removals*/};
297
+ const select = this.e.select, self = this;
298
+ Object.keys(select.options).forEach(function(key){
299
+ const opt = select.options[key];
287300
const stashed = $stash.getWinfo({name:opt.value});
288301
if(stashed){
289302
const isNew = 'sandbox'===stashed.type ? false : !stashed.version;
290303
D.addClass(opt, isNew ? 'stashed-new' :'stashed');
291304
}else{
292305
D.removeClass(opt, 'stashed', 'stashed-new');
293306
}
307
+ self.cache.names[opt.value] = true;
294308
});
295309
},
296310
/** Removes the given wiki page entry from the page selection
297311
list, if it's in the list. */
298312
removeEntry: function(name){
@@ -304,45 +318,59 @@
304318
sel.options.remove(sel.selectedIndex);
305319
}
306320
sel.selectedIndex = ndx;
307321
},
308322
309
- /** Loads the page list and populates the selection list. */
310
- loadList: function callee(){
311
- delete this.pageMap;
323
+ /**
324
+ Rebuilds the selection list. Necessary when it's loaded from
325
+ the server or we locally create a new page. */
326
+ _rebuildList: function callee(){
327
+ /* Jump through some hoops to integrate new/unsaved
328
+ pages into the list of existing pages... We use a map
329
+ as an intermediary in order to filter out any local-stash
330
+ dupes from server-side copies. */
331
+ const list = this.cache.pageList;
332
+ if(!list) return;
312333
if(!callee.sorticase){
313334
callee.sorticase = function(l,r){
335
+ if(l===r) return 0;
314336
l = l.toLowerCase();
315337
r = r.toLowerCase();
316338
return l<=r ? -1 : 1;
317339
};
340
+ }
341
+ const map = {}, ndx = $stash.getIndex(), sel = this.e.select;
342
+ D.clearElement(sel);
343
+ list.forEach((winfo)=>map[winfo.name] = winfo);
344
+ Object.keys(ndx).forEach(function(key){
345
+ const winfo = ndx[key];
346
+ if(!winfo.version/*new page*/) map[winfo.name] = winfo;
347
+ });
348
+ const self = this;
349
+ Object.keys(map)
350
+ .sort(callee.sorticase)
351
+ .forEach(function(name){
352
+ const winfo = map[name];
353
+ const opt = D.option(sel, winfo.name);
354
+ const wtype = opt.dataset.wtype =
355
+ winfo.type==='sandbox' ? 'normal' : (winfo.type||'normal');
356
+ const cb = self.e.filterCheckboxes[wtype];
357
+ if(cb && !cb.checked) D.addClass(opt, 'hidden');
358
+ });
359
+ D.enable(sel);
360
+ if(P.winfo) sel.value = P.winfo.name;
361
+ this.refreshStashMarks();
362
+ },
363
+
364
+ /** Loads the page list and populates the selection list. */
365
+ loadList: function callee(){
366
+ delete this.pageMap;
367
+ if(!callee.onload){
318368
const self = this;
319369
callee.onload = function(list){
320
- /* Jump through some hoops to integrate new/unsaved
321
- pages into the list of existing pages... We use a map
322
- as an intermediary in order to filter out any local-stash
323
- dupes from server-side copies. */
324
- const map = {}, ndx = $stash.getIndex(), sel = self.e.select;
325
- D.clearElement(sel);
326
- list.forEach((winfo)=>map[winfo.name] = winfo);
327
- Object.keys(ndx).forEach(function(key){
328
- const winfo = ndx[key];
329
- if(!winfo.version/*new page*/) map[winfo.name] = winfo;
330
- });
331
- Object.keys(map)
332
- .sort(callee.sorticase)
333
- .forEach(function(name){
334
- const winfo = map[name];
335
- const opt = D.option(sel, winfo.name);
336
- const wtype = opt.dataset.wtype =
337
- winfo.type==='sandbox' ? 'normal' : winfo.type;
338
- const cb = self.e.filterCheckboxes[wtype];
339
- if(cb && !cb.checked) D.addClass(opt, 'hidden');
340
- });
341
- D.enable(sel);
342
- if(P.winfo) sel.value = P.winfo.name;
343
- self.refreshStashMarks();
370
+ self.cache.pageList = list;
371
+ self._rebuildList();
344372
F.message("Loaded page list.");
345373
};
346374
}
347375
F.fetch('wikiajax/list',{
348376
urlParams:{verbose:true},
@@ -349,18 +377,75 @@
349377
responseType: 'json',
350378
onload: callee.onload
351379
});
352380
return this;
353381
},
382
+
383
+ /**
384
+ Returns true if the given name appears to be a valid
385
+ wiki page name, noting that the final arbitrator is the
386
+ server. On validation error it emits a message via fossil.error()
387
+ and returns false.
388
+ */
389
+ validatePageName: function(name){
390
+ var err;
391
+ if(!name){
392
+ err = "may not be empty";
393
+ }else if(this.cache.names.hasOwnProperty(name)){
394
+ err = "page already exists: "+name;
395
+ }else if(name.length>100){
396
+ err = "too long (limit is 100)";
397
+ }else if(/\s{2,}/.test(name)){
398
+ err = "multiple consecutive spaces";
399
+ }else if(/[\t\r\n]/.test(name)){
400
+ err = "contains control character(s)";
401
+ }else{
402
+ let i = 0, n = name.length, c;
403
+ for( ; i < n; ++i ){
404
+ if(name.charCodeAt(i)<0x20){
405
+ err = "contains control character(s)";
406
+ break;
407
+ }
408
+ }
409
+ }
410
+ if(err){
411
+ F.error("Invalid name:",err);
412
+ }
413
+ return !err;
414
+ },
415
+
416
+ /**
417
+ If the given name is valid, a new page with that (trimmed) name
418
+ is added to the local stash.
419
+ */
420
+ addNewPage: function(name){
421
+ name = name.trim();
422
+ if(!this.validatePageName(name)) return false;
423
+ var wtype = 'normal';
424
+ if(0===name.indexOf('checkin/')) wtype = 'checkin';
425
+ else if(0===name.indexOf('branch/')) wtype = 'branch';
426
+ else if(0===name.indexOf('tag/')) wtype = 'tag';
427
+ /* ^^^ note that we're not validating that, e.g., checkin/XYZ
428
+ has a full artifact ID after "checkin/". */
429
+ const winfo = {
430
+ name: name, type: wtype, mimetype: 'text/x-fossil-wiki',
431
+ version: null, parent: null
432
+ };
433
+ $stash.updateWinfo(winfo, '');
434
+ this._rebuildList();
435
+ P.loadPage(winfo.name);
436
+ return true;
437
+ },
438
+
354439
/**
355440
Installs a wiki page selection list into the given parent DOM
356441
element and loads the page list from the server.
357442
*/
358443
init: function(parentElem){
359
- const sel = D.select(), btn = D.button("Reload page list");
444
+ const sel = D.select(), btn = D.addClass(D.button("Reload page list"), 'save');
360445
this.e.select = sel;
361
- D.addClass(parentElem, 'wikiedit-page-list-wrapper');
446
+ D.addClass(parentElem, 'WikiList');
362447
D.clearElement(parentElem);
363448
D.append(
364449
parentElem,
365450
D.append(D.fieldset("Select a page to edit"),
366451
sel)
@@ -411,15 +496,36 @@
411496
D.append(D.span(), P.config.editStateMarkers.isModified,
412497
" = page has local edits"),
413498
D.append(D.span(), P.config.editStateMarkers.isNew,
414499
" = page is new/unsaved")
415500
);
501
+
502
+ const fsNewPage = D.fieldset("Create new page"),
503
+ fsNewPageBody = D.div(),
504
+ newPageName = D.input('text'),
505
+ newPageBtn = D.button("Add page locally")
506
+ ;
507
+ D.append(parentElem, fsNewPage);
508
+ D.append(fsNewPage, fsNewPageBody);
509
+ D.addClass(fsNewPageBody, 'flex-container', 'flex-column', 'new-page');
510
+ D.append(
511
+ fsNewPageBody, newPageName, newPageBtn,
512
+ D.append(D.addClass(D.span(), 'mini-tip'),
513
+ "New pages exist only in this browser until they are saved.")
514
+ );
515
+ newPageBtn.addEventListener('click', function(){
516
+ if(self.addNewPage(newPageName.value)){
517
+ newPageName.value = '';
518
+ }
519
+ }, false);
520
+
416521
D.append(
417522
parentElem,
418523
D.append(D.addClass(D.div(), 'fieldset-wrapper'),
419
- fsFilter, fsLegend)
524
+ fsFilter, fsNewPage, fsLegend)
420525
);
526
+
421527
D.append(parentElem, btn);
422528
btn.addEventListener('click', ()=>this.loadList(), false);
423529
this.loadList();
424530
sel.addEventListener('change', (e)=>P.loadPage(e.target.value), false);
425531
F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks(), false);
@@ -502,11 +608,11 @@
502608
functionality and visibility. */
503609
E('#fossil-status-bar'), P.tabs.e.tabs
504610
);
505611
506612
P.tabs.addEventListener(
507
- /* Set up auto-refresh of the preview tab... */
613
+ /* Set up some before-switch-to tab event tasks... */
508614
'before-switch-to', function(ev){
509615
if(ev.detail===P.e.tabs.preview){
510616
P.baseHrefForWiki();
511617
if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
512618
}else if(ev.detail===P.e.tabs.diff){
@@ -519,10 +625,11 @@
519625
is hidden (and therefore P.e.diffTarget is also hidden).
520626
*/
521627
D.removeClass(P.e.diffTarget, 'hidden');
522628
}else if(ev.detail===P.e.tabs.save){
523629
const btn = P.e.btnSave;
630
+ P.updateAttachmentView();
524631
if(!P.winfo || !P.getStashedWinfo(P.winfo)){
525632
D.disable(btn).innerText =
526633
"There are no changes to save";
527634
}else{
528635
D.enable(btn).innerText = "Save changes";
@@ -577,13 +684,13 @@
577684
}
578685
P.unstashContent()
579686
if(w.version || w.type==='sandbox'){
580687
P.loadPage();
581688
}else{
582
- delete P.winfo;
583689
WikiList.removeEntry(w.name);
584690
P.updatePageTitle();
691
+ delete P.winfo;
585692
F.message("Discarded new page ["+w.name+"].");
586693
}
587694
},
588695
ticks: 3
589696
});
@@ -662,15 +769,15 @@
662769
P.wikiContent(winfo.content || '');
663770
WikiList.e.select.value = winfo.name;
664771
if(!winfo.version && winfo.type!=='sandbox'){
665772
F.error('You are editing a new, unsaved page:',winfo.name);
666773
}
667
- P.updateAttachmentView().updatePageTitle();
774
+ P.updatePageTitle();
668775
},
669776
false
670777
);
671
- P.updateAttachmentView();
778
+ P.updatePageTitle().updateAttachmentView();
672779
}/*F.onPageLoad()*/);
673780
674781
/**
675782
Returns true if fossil.page.winfo is set, indicating that a page
676783
has been loaded, else it reports an error and returns false.
@@ -711,14 +818,16 @@
711818
const wrapper = P.e.attachmentWrapper;
712819
D.clearElement(wrapper);
713820
const ul = D.ul();
714821
D.append(wrapper, ul);
715822
if(!P.winfo){
716
- D.append(D.li(ul), "No page loaded.");
823
+ D.append(D.li(ul),
824
+ "Load a page to get access to its attachment-related pages.");
717825
return this;
718826
}else if(!P.winfo.version){
719
- D.append(D.li(ul), "A new/unsaved page cannot have attachments.");
827
+ D.append(D.li(ul),
828
+ "A new/unsaved page cannot have attachments. Save it first.");
720829
return this;
721830
}
722831
const wi = P.winfo;
723832
D.append(
724833
D.li(ul),
725834
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -269,30 +269,44 @@
269 // Force UI update
270 s.dispatchEvent(new Event('change',{target:s}));
271 }
272 };
273
 
 
 
274 const WikiList = {
275 e: {
276 filterCheckboxes: {
277 /*map of wiki page type to checkbox for list filtering purposes,
278 except for "sandbox" type, which is assumed to be covered by
279 the "normal" type filter. */}
 
 
 
 
 
 
 
 
 
280 },
281 /** Updates OPTION elements to reflect whether the page has
282 local changes or is new/unsaved. */
283 refreshStashMarks: function(){
284 const sel = this.e.select;
285 Object.keys(sel.options).forEach(function(key){
286 const opt = sel.options[key];
 
287 const stashed = $stash.getWinfo({name:opt.value});
288 if(stashed){
289 const isNew = 'sandbox'===stashed.type ? false : !stashed.version;
290 D.addClass(opt, isNew ? 'stashed-new' :'stashed');
291 }else{
292 D.removeClass(opt, 'stashed', 'stashed-new');
293 }
 
294 });
295 },
296 /** Removes the given wiki page entry from the page selection
297 list, if it's in the list. */
298 removeEntry: function(name){
@@ -304,45 +318,59 @@
304 sel.options.remove(sel.selectedIndex);
305 }
306 sel.selectedIndex = ndx;
307 },
308
309 /** Loads the page list and populates the selection list. */
310 loadList: function callee(){
311 delete this.pageMap;
 
 
 
 
 
 
 
312 if(!callee.sorticase){
313 callee.sorticase = function(l,r){
 
314 l = l.toLowerCase();
315 r = r.toLowerCase();
316 return l<=r ? -1 : 1;
317 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318 const self = this;
319 callee.onload = function(list){
320 /* Jump through some hoops to integrate new/unsaved
321 pages into the list of existing pages... We use a map
322 as an intermediary in order to filter out any local-stash
323 dupes from server-side copies. */
324 const map = {}, ndx = $stash.getIndex(), sel = self.e.select;
325 D.clearElement(sel);
326 list.forEach((winfo)=>map[winfo.name] = winfo);
327 Object.keys(ndx).forEach(function(key){
328 const winfo = ndx[key];
329 if(!winfo.version/*new page*/) map[winfo.name] = winfo;
330 });
331 Object.keys(map)
332 .sort(callee.sorticase)
333 .forEach(function(name){
334 const winfo = map[name];
335 const opt = D.option(sel, winfo.name);
336 const wtype = opt.dataset.wtype =
337 winfo.type==='sandbox' ? 'normal' : winfo.type;
338 const cb = self.e.filterCheckboxes[wtype];
339 if(cb && !cb.checked) D.addClass(opt, 'hidden');
340 });
341 D.enable(sel);
342 if(P.winfo) sel.value = P.winfo.name;
343 self.refreshStashMarks();
344 F.message("Loaded page list.");
345 };
346 }
347 F.fetch('wikiajax/list',{
348 urlParams:{verbose:true},
@@ -349,18 +377,75 @@
349 responseType: 'json',
350 onload: callee.onload
351 });
352 return this;
353 },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354 /**
355 Installs a wiki page selection list into the given parent DOM
356 element and loads the page list from the server.
357 */
358 init: function(parentElem){
359 const sel = D.select(), btn = D.button("Reload page list");
360 this.e.select = sel;
361 D.addClass(parentElem, 'wikiedit-page-list-wrapper');
362 D.clearElement(parentElem);
363 D.append(
364 parentElem,
365 D.append(D.fieldset("Select a page to edit"),
366 sel)
@@ -411,15 +496,36 @@
411 D.append(D.span(), P.config.editStateMarkers.isModified,
412 " = page has local edits"),
413 D.append(D.span(), P.config.editStateMarkers.isNew,
414 " = page is new/unsaved")
415 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416 D.append(
417 parentElem,
418 D.append(D.addClass(D.div(), 'fieldset-wrapper'),
419 fsFilter, fsLegend)
420 );
 
421 D.append(parentElem, btn);
422 btn.addEventListener('click', ()=>this.loadList(), false);
423 this.loadList();
424 sel.addEventListener('change', (e)=>P.loadPage(e.target.value), false);
425 F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks(), false);
@@ -502,11 +608,11 @@
502 functionality and visibility. */
503 E('#fossil-status-bar'), P.tabs.e.tabs
504 );
505
506 P.tabs.addEventListener(
507 /* Set up auto-refresh of the preview tab... */
508 'before-switch-to', function(ev){
509 if(ev.detail===P.e.tabs.preview){
510 P.baseHrefForWiki();
511 if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
512 }else if(ev.detail===P.e.tabs.diff){
@@ -519,10 +625,11 @@
519 is hidden (and therefore P.e.diffTarget is also hidden).
520 */
521 D.removeClass(P.e.diffTarget, 'hidden');
522 }else if(ev.detail===P.e.tabs.save){
523 const btn = P.e.btnSave;
 
524 if(!P.winfo || !P.getStashedWinfo(P.winfo)){
525 D.disable(btn).innerText =
526 "There are no changes to save";
527 }else{
528 D.enable(btn).innerText = "Save changes";
@@ -577,13 +684,13 @@
577 }
578 P.unstashContent()
579 if(w.version || w.type==='sandbox'){
580 P.loadPage();
581 }else{
582 delete P.winfo;
583 WikiList.removeEntry(w.name);
584 P.updatePageTitle();
 
585 F.message("Discarded new page ["+w.name+"].");
586 }
587 },
588 ticks: 3
589 });
@@ -662,15 +769,15 @@
662 P.wikiContent(winfo.content || '');
663 WikiList.e.select.value = winfo.name;
664 if(!winfo.version && winfo.type!=='sandbox'){
665 F.error('You are editing a new, unsaved page:',winfo.name);
666 }
667 P.updateAttachmentView().updatePageTitle();
668 },
669 false
670 );
671 P.updateAttachmentView();
672 }/*F.onPageLoad()*/);
673
674 /**
675 Returns true if fossil.page.winfo is set, indicating that a page
676 has been loaded, else it reports an error and returns false.
@@ -711,14 +818,16 @@
711 const wrapper = P.e.attachmentWrapper;
712 D.clearElement(wrapper);
713 const ul = D.ul();
714 D.append(wrapper, ul);
715 if(!P.winfo){
716 D.append(D.li(ul), "No page loaded.");
 
717 return this;
718 }else if(!P.winfo.version){
719 D.append(D.li(ul), "A new/unsaved page cannot have attachments.");
 
720 return this;
721 }
722 const wi = P.winfo;
723 D.append(
724 D.li(ul),
725
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -269,30 +269,44 @@
269 // Force UI update
270 s.dispatchEvent(new Event('change',{target:s}));
271 }
272 };
273
274 /**
275 Sets up and maintains the widgets for the list of wiki pages.
276 */
277 const WikiList = {
278 e: {
279 filterCheckboxes: {
280 /*map of wiki page type to checkbox for list filtering purposes,
281 except for "sandbox" type, which is assumed to be covered by
282 the "normal" type filter. */},
283 },
284 cache: {
285 names: {
286 /* Map of page names to "something." We don't map to their
287 winfo bits because those regularly get swapped out via
288 de/serialization. We need this map to support the add-new-page
289 feature, to give us a way to check for dupes without asking
290 the server or walking through the whole selection list.
291 */}
292 },
293 /** Updates OPTION elements to reflect whether the page has
294 local changes or is new/unsaved. */
295 refreshStashMarks: function(){
296 this.cache.names = {/*must reset it to acount for local page removals*/};
297 const select = this.e.select, self = this;
298 Object.keys(select.options).forEach(function(key){
299 const opt = select.options[key];
300 const stashed = $stash.getWinfo({name:opt.value});
301 if(stashed){
302 const isNew = 'sandbox'===stashed.type ? false : !stashed.version;
303 D.addClass(opt, isNew ? 'stashed-new' :'stashed');
304 }else{
305 D.removeClass(opt, 'stashed', 'stashed-new');
306 }
307 self.cache.names[opt.value] = true;
308 });
309 },
310 /** Removes the given wiki page entry from the page selection
311 list, if it's in the list. */
312 removeEntry: function(name){
@@ -304,45 +318,59 @@
318 sel.options.remove(sel.selectedIndex);
319 }
320 sel.selectedIndex = ndx;
321 },
322
323 /**
324 Rebuilds the selection list. Necessary when it's loaded from
325 the server or we locally create a new page. */
326 _rebuildList: function callee(){
327 /* Jump through some hoops to integrate new/unsaved
328 pages into the list of existing pages... We use a map
329 as an intermediary in order to filter out any local-stash
330 dupes from server-side copies. */
331 const list = this.cache.pageList;
332 if(!list) return;
333 if(!callee.sorticase){
334 callee.sorticase = function(l,r){
335 if(l===r) return 0;
336 l = l.toLowerCase();
337 r = r.toLowerCase();
338 return l<=r ? -1 : 1;
339 };
340 }
341 const map = {}, ndx = $stash.getIndex(), sel = this.e.select;
342 D.clearElement(sel);
343 list.forEach((winfo)=>map[winfo.name] = winfo);
344 Object.keys(ndx).forEach(function(key){
345 const winfo = ndx[key];
346 if(!winfo.version/*new page*/) map[winfo.name] = winfo;
347 });
348 const self = this;
349 Object.keys(map)
350 .sort(callee.sorticase)
351 .forEach(function(name){
352 const winfo = map[name];
353 const opt = D.option(sel, winfo.name);
354 const wtype = opt.dataset.wtype =
355 winfo.type==='sandbox' ? 'normal' : (winfo.type||'normal');
356 const cb = self.e.filterCheckboxes[wtype];
357 if(cb && !cb.checked) D.addClass(opt, 'hidden');
358 });
359 D.enable(sel);
360 if(P.winfo) sel.value = P.winfo.name;
361 this.refreshStashMarks();
362 },
363
364 /** Loads the page list and populates the selection list. */
365 loadList: function callee(){
366 delete this.pageMap;
367 if(!callee.onload){
368 const self = this;
369 callee.onload = function(list){
370 self.cache.pageList = list;
371 self._rebuildList();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372 F.message("Loaded page list.");
373 };
374 }
375 F.fetch('wikiajax/list',{
376 urlParams:{verbose:true},
@@ -349,18 +377,75 @@
377 responseType: 'json',
378 onload: callee.onload
379 });
380 return this;
381 },
382
383 /**
384 Returns true if the given name appears to be a valid
385 wiki page name, noting that the final arbitrator is the
386 server. On validation error it emits a message via fossil.error()
387 and returns false.
388 */
389 validatePageName: function(name){
390 var err;
391 if(!name){
392 err = "may not be empty";
393 }else if(this.cache.names.hasOwnProperty(name)){
394 err = "page already exists: "+name;
395 }else if(name.length>100){
396 err = "too long (limit is 100)";
397 }else if(/\s{2,}/.test(name)){
398 err = "multiple consecutive spaces";
399 }else if(/[\t\r\n]/.test(name)){
400 err = "contains control character(s)";
401 }else{
402 let i = 0, n = name.length, c;
403 for( ; i < n; ++i ){
404 if(name.charCodeAt(i)<0x20){
405 err = "contains control character(s)";
406 break;
407 }
408 }
409 }
410 if(err){
411 F.error("Invalid name:",err);
412 }
413 return !err;
414 },
415
416 /**
417 If the given name is valid, a new page with that (trimmed) name
418 is added to the local stash.
419 */
420 addNewPage: function(name){
421 name = name.trim();
422 if(!this.validatePageName(name)) return false;
423 var wtype = 'normal';
424 if(0===name.indexOf('checkin/')) wtype = 'checkin';
425 else if(0===name.indexOf('branch/')) wtype = 'branch';
426 else if(0===name.indexOf('tag/')) wtype = 'tag';
427 /* ^^^ note that we're not validating that, e.g., checkin/XYZ
428 has a full artifact ID after "checkin/". */
429 const winfo = {
430 name: name, type: wtype, mimetype: 'text/x-fossil-wiki',
431 version: null, parent: null
432 };
433 $stash.updateWinfo(winfo, '');
434 this._rebuildList();
435 P.loadPage(winfo.name);
436 return true;
437 },
438
439 /**
440 Installs a wiki page selection list into the given parent DOM
441 element and loads the page list from the server.
442 */
443 init: function(parentElem){
444 const sel = D.select(), btn = D.addClass(D.button("Reload page list"), 'save');
445 this.e.select = sel;
446 D.addClass(parentElem, 'WikiList');
447 D.clearElement(parentElem);
448 D.append(
449 parentElem,
450 D.append(D.fieldset("Select a page to edit"),
451 sel)
@@ -411,15 +496,36 @@
496 D.append(D.span(), P.config.editStateMarkers.isModified,
497 " = page has local edits"),
498 D.append(D.span(), P.config.editStateMarkers.isNew,
499 " = page is new/unsaved")
500 );
501
502 const fsNewPage = D.fieldset("Create new page"),
503 fsNewPageBody = D.div(),
504 newPageName = D.input('text'),
505 newPageBtn = D.button("Add page locally")
506 ;
507 D.append(parentElem, fsNewPage);
508 D.append(fsNewPage, fsNewPageBody);
509 D.addClass(fsNewPageBody, 'flex-container', 'flex-column', 'new-page');
510 D.append(
511 fsNewPageBody, newPageName, newPageBtn,
512 D.append(D.addClass(D.span(), 'mini-tip'),
513 "New pages exist only in this browser until they are saved.")
514 );
515 newPageBtn.addEventListener('click', function(){
516 if(self.addNewPage(newPageName.value)){
517 newPageName.value = '';
518 }
519 }, false);
520
521 D.append(
522 parentElem,
523 D.append(D.addClass(D.div(), 'fieldset-wrapper'),
524 fsFilter, fsNewPage, fsLegend)
525 );
526
527 D.append(parentElem, btn);
528 btn.addEventListener('click', ()=>this.loadList(), false);
529 this.loadList();
530 sel.addEventListener('change', (e)=>P.loadPage(e.target.value), false);
531 F.page.addEventListener('wiki-stash-updated', ()=>this.refreshStashMarks(), false);
@@ -502,11 +608,11 @@
608 functionality and visibility. */
609 E('#fossil-status-bar'), P.tabs.e.tabs
610 );
611
612 P.tabs.addEventListener(
613 /* Set up some before-switch-to tab event tasks... */
614 'before-switch-to', function(ev){
615 if(ev.detail===P.e.tabs.preview){
616 P.baseHrefForWiki();
617 if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview();
618 }else if(ev.detail===P.e.tabs.diff){
@@ -519,10 +625,11 @@
625 is hidden (and therefore P.e.diffTarget is also hidden).
626 */
627 D.removeClass(P.e.diffTarget, 'hidden');
628 }else if(ev.detail===P.e.tabs.save){
629 const btn = P.e.btnSave;
630 P.updateAttachmentView();
631 if(!P.winfo || !P.getStashedWinfo(P.winfo)){
632 D.disable(btn).innerText =
633 "There are no changes to save";
634 }else{
635 D.enable(btn).innerText = "Save changes";
@@ -577,13 +684,13 @@
684 }
685 P.unstashContent()
686 if(w.version || w.type==='sandbox'){
687 P.loadPage();
688 }else{
 
689 WikiList.removeEntry(w.name);
690 P.updatePageTitle();
691 delete P.winfo;
692 F.message("Discarded new page ["+w.name+"].");
693 }
694 },
695 ticks: 3
696 });
@@ -662,15 +769,15 @@
769 P.wikiContent(winfo.content || '');
770 WikiList.e.select.value = winfo.name;
771 if(!winfo.version && winfo.type!=='sandbox'){
772 F.error('You are editing a new, unsaved page:',winfo.name);
773 }
774 P.updatePageTitle();
775 },
776 false
777 );
778 P.updatePageTitle().updateAttachmentView();
779 }/*F.onPageLoad()*/);
780
781 /**
782 Returns true if fossil.page.winfo is set, indicating that a page
783 has been loaded, else it reports an error and returns false.
@@ -711,14 +818,16 @@
818 const wrapper = P.e.attachmentWrapper;
819 D.clearElement(wrapper);
820 const ul = D.ul();
821 D.append(wrapper, ul);
822 if(!P.winfo){
823 D.append(D.li(ul),
824 "Load a page to get access to its attachment-related pages.");
825 return this;
826 }else if(!P.winfo.version){
827 D.append(D.li(ul),
828 "A new/unsaved page cannot have attachments. Save it first.");
829 return this;
830 }
831 const wi = P.winfo;
832 D.append(
833 D.li(ul),
834
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -6,11 +6,11 @@
66
body.wikiedit textarea:focus,
77
body.wikiedit input,
88
body.wikiedit input:focus{
99
/* The sudden appearance of a border (as in the Ardoise skin)
1010
shifts the layout in unsightly ways */
11
- border: reset;
11
+ border: revert;
1212
}
1313
body.wikiedit div.wikiedit-preview {
1414
margin: 0;
1515
padding: 0;
1616
}
@@ -43,47 +43,66 @@
4343
margin: 0.25em;
4444
}
4545
body.wikiedit .wikiedit-options.flex-container.flex-row {
4646
align-items: first baseline;
4747
}
48
-body.wikiedit .wikiedit-page-list-wrapper {
48
+body.wikiedit .WikiList {
4949
display: flex;
5050
flex-direction: column;
51
- flex-wrap: wrap;
52
- justify-content: center;
5351
align-items: start;
5452
}
55
-body.wikiedit .wikiedit-page-list-wrapper select option {
53
+body.wikiedit .WikiList select {
54
+ font-size: 110%;
55
+}
56
+body.wikiedit .WikiList select option {
5657
margin: 0.5em 0;
5758
}
58
-body.wikiedit .wikiedit-page-list-wrapper select option.stashed::before {
59
+body.wikiedit .WikiList select option.stashed::before {
5960
/* Maintenance reminder: the option.stashed/stashed-new "content" values
6061
are duplicated in fossil.page.wikiedit.js and need to be changed there
6162
if they are changed here: see fossil.page.config.editStateMarkers */
6263
content: "[*] ";
6364
}
64
-body.wikiedit .wikiedit-page-list-wrapper select option.stashed-new::before {
65
+body.wikiedit .WikiList select option.stashed-new::before {
6566
content: "[+] ";
6667
}
6768
body.wikiedit textarea {
6869
max-width: calc(100% - 1em);
6970
}
7071
body.wikiedit .tabs .tab-panel {
7172
/* Needed for wide diffs */
7273
overflow: auto;
7374
}
74
-body.wikiedit .wikiedit-page-list-wrapper fieldset {
75
+body.wikiedit .WikiList fieldset {
7576
padding: 0.25em;
7677
}
77
-body.wikiedit .wikiedit-page-list-wrapper fieldset > :not(legend) {
78
+body.wikiedit .WikiList legend {
79
+ font-size: 90%;
80
+}
81
+body.wikiedit .WikiList fieldset > :not(legend) {
7882
/* Stretch page selection list when it's empty or only has short page names */
7983
width: 100%;
8084
}
81
-body.wikiedit .wikiedit-page-list-wrapper .fieldset-wrapper {
85
+body.wikiedit .WikiList .fieldset-wrapper {
8286
/* Container for the filter and edit status fieldsets */
8387
display: flex;
8488
flex-direction: row;
8589
flex-wrap: wrap;
8690
align-items: stretch;
8791
justify-content: stretch;
88
- margin: 0 0 1em 0;
92
+ margin: 0;
93
+}
94
+body.wikiedit .WikiList button.save {
95
+ margin: 1em 0 0 0;
96
+}
97
+body.wikiedit .WikiList .new-page {
98
+ align-items: flex-start;
99
+ max-width: 15em;
100
+}
101
+body.wikiedit .WikiList .new-page input {
102
+}
103
+body.wikiedit #wikiedit-tab-save h3 {
104
+ margin: 0;
105
+}
106
+body.wikiedit span.mini-tip {
107
+ font-size: 80%;
89108
}
90109
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -6,11 +6,11 @@
6 body.wikiedit textarea:focus,
7 body.wikiedit input,
8 body.wikiedit input:focus{
9 /* The sudden appearance of a border (as in the Ardoise skin)
10 shifts the layout in unsightly ways */
11 border: reset;
12 }
13 body.wikiedit div.wikiedit-preview {
14 margin: 0;
15 padding: 0;
16 }
@@ -43,47 +43,66 @@
43 margin: 0.25em;
44 }
45 body.wikiedit .wikiedit-options.flex-container.flex-row {
46 align-items: first baseline;
47 }
48 body.wikiedit .wikiedit-page-list-wrapper {
49 display: flex;
50 flex-direction: column;
51 flex-wrap: wrap;
52 justify-content: center;
53 align-items: start;
54 }
55 body.wikiedit .wikiedit-page-list-wrapper select option {
 
 
 
56 margin: 0.5em 0;
57 }
58 body.wikiedit .wikiedit-page-list-wrapper select option.stashed::before {
59 /* Maintenance reminder: the option.stashed/stashed-new "content" values
60 are duplicated in fossil.page.wikiedit.js and need to be changed there
61 if they are changed here: see fossil.page.config.editStateMarkers */
62 content: "[*] ";
63 }
64 body.wikiedit .wikiedit-page-list-wrapper select option.stashed-new::before {
65 content: "[+] ";
66 }
67 body.wikiedit textarea {
68 max-width: calc(100% - 1em);
69 }
70 body.wikiedit .tabs .tab-panel {
71 /* Needed for wide diffs */
72 overflow: auto;
73 }
74 body.wikiedit .wikiedit-page-list-wrapper fieldset {
75 padding: 0.25em;
76 }
77 body.wikiedit .wikiedit-page-list-wrapper fieldset > :not(legend) {
 
 
 
78 /* Stretch page selection list when it's empty or only has short page names */
79 width: 100%;
80 }
81 body.wikiedit .wikiedit-page-list-wrapper .fieldset-wrapper {
82 /* Container for the filter and edit status fieldsets */
83 display: flex;
84 flex-direction: row;
85 flex-wrap: wrap;
86 align-items: stretch;
87 justify-content: stretch;
88 margin: 0 0 1em 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89 }
90
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -6,11 +6,11 @@
6 body.wikiedit textarea:focus,
7 body.wikiedit input,
8 body.wikiedit input:focus{
9 /* The sudden appearance of a border (as in the Ardoise skin)
10 shifts the layout in unsightly ways */
11 border: revert;
12 }
13 body.wikiedit div.wikiedit-preview {
14 margin: 0;
15 padding: 0;
16 }
@@ -43,47 +43,66 @@
43 margin: 0.25em;
44 }
45 body.wikiedit .wikiedit-options.flex-container.flex-row {
46 align-items: first baseline;
47 }
48 body.wikiedit .WikiList {
49 display: flex;
50 flex-direction: column;
 
 
51 align-items: start;
52 }
53 body.wikiedit .WikiList select {
54 font-size: 110%;
55 }
56 body.wikiedit .WikiList select option {
57 margin: 0.5em 0;
58 }
59 body.wikiedit .WikiList select option.stashed::before {
60 /* Maintenance reminder: the option.stashed/stashed-new "content" values
61 are duplicated in fossil.page.wikiedit.js and need to be changed there
62 if they are changed here: see fossil.page.config.editStateMarkers */
63 content: "[*] ";
64 }
65 body.wikiedit .WikiList select option.stashed-new::before {
66 content: "[+] ";
67 }
68 body.wikiedit textarea {
69 max-width: calc(100% - 1em);
70 }
71 body.wikiedit .tabs .tab-panel {
72 /* Needed for wide diffs */
73 overflow: auto;
74 }
75 body.wikiedit .WikiList fieldset {
76 padding: 0.25em;
77 }
78 body.wikiedit .WikiList legend {
79 font-size: 90%;
80 }
81 body.wikiedit .WikiList fieldset > :not(legend) {
82 /* Stretch page selection list when it's empty or only has short page names */
83 width: 100%;
84 }
85 body.wikiedit .WikiList .fieldset-wrapper {
86 /* Container for the filter and edit status fieldsets */
87 display: flex;
88 flex-direction: row;
89 flex-wrap: wrap;
90 align-items: stretch;
91 justify-content: stretch;
92 margin: 0;
93 }
94 body.wikiedit .WikiList button.save {
95 margin: 1em 0 0 0;
96 }
97 body.wikiedit .WikiList .new-page {
98 align-items: flex-start;
99 max-width: 15em;
100 }
101 body.wikiedit .WikiList .new-page input {
102 }
103 body.wikiedit #wikiedit-tab-save h3 {
104 margin: 0;
105 }
106 body.wikiedit span.mini-tip {
107 font-size: 80%;
108 }
109
+22 -25
--- src/wiki.c
+++ src/wiki.c
@@ -1131,24 +1131,27 @@
11311131
CX("<div id='wikiedit-tab-content' "
11321132
"data-tab-parent='wikiedit-tabs' "
11331133
"data-tab-label='Wiki Editor'"
11341134
">");
11351135
CX("<div class='flex-container flex-row child-gap-small'>");
1136
+ CX("<span class='input-with-label'>"
1137
+ "<label>Mime type</label>");
11361138
mimetype_option_menu(0);
1137
- CX("<button class='wikiedit-content-reload' "
1138
- "title='Reload the file from the server, discarding "
1139
- "any local edits. To help avoid accidental loss of "
1140
- "edits, it requires confirmation (a second click) within "
1141
- "a few seconds or it will not reload.'"
1142
- ">Discard &amp; Reload</button>");
1139
+ CX("</span>");
11431140
style_select_list_int("select-font-size",
11441141
"editor_font_size", "Editor font size",
11451142
NULL/*tooltip*/,
11461143
100,
11471144
"100%", 100, "125%", 125,
11481145
"150%", 150, "175%", 175,
11491146
"200%", 200, NULL);
1147
+ CX("<button class='wikiedit-content-reload' "
1148
+ "title='Reload the file from the server, discarding "
1149
+ "any local edits. To help avoid accidental loss of "
1150
+ "edits, it requires confirmation (a second click) within "
1151
+ "a few seconds or it will not reload.'"
1152
+ ">Discard &amp; Reload</button>");
11501153
CX("</div>");
11511154
CX("<div class='flex-container flex-column stretch'>");
11521155
CX("<textarea name='content' id='wikiedit-content-editor' "
11531156
"class='wikiedit' "
11541157
"rows='25' cols='80'>");
@@ -1194,25 +1197,10 @@
11941197
11951198
CX("<div class='wikiedit-options flex-container flex-row' "
11961199
"id='wikiedit-tab-diff-buttons'>");
11971200
CX("<button class='sbs'>Side-by-side</button>"
11981201
"<button class='unified'>Unified</button>");
1199
- if(0){
1200
- /* For the time being let's just ignore all whitespace
1201
- ** changes, as files with Windows-style EOLs always show
1202
- ** more diffs than we want then they're submitted to
1203
- ** ?ajax=diff because JS normalizes them to Unix EOLs.
1204
- ** We can revisit this decision later. */
1205
- style_select_list_int("diff-ws-policy",
1206
- "diff_ws", "Whitespace",
1207
- "Whitespace handling policy.",
1208
- 2,
1209
- "Diff all whitespace", 0,
1210
- "Ignore EOL whitespace", 1,
1211
- "Ignore all whitespace", 2,
1212
- NULL);
1213
- }
12141202
CX("</div>");
12151203
CX("<div id='wikiedit-tab-diff-wrapper'>"
12161204
"Diffs will be shown here."
12171205
"</div>");
12181206
CX("</div>"/*#wikiedit-tab-diff*/);
@@ -1219,23 +1207,32 @@
12191207
}
12201208
12211209
{
12221210
CX("<div id='wikiedit-tab-save' "
12231211
"data-tab-parent='wikiedit-tabs' "
1224
- "data-tab-label='Save, etc.'"
1212
+ "data-tab-label='Save, Help, etc.'"
12251213
">");
12261214
CX("<button class='wikiedit-save'>Save</button>");
12271215
CX("<hr>");
1228
- CX("Wiki formatting rules:");
1216
+ CX("<h3>Wiki formatting rules</h3>");
12291217
CX("<ul>");
12301218
CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>");
12311219
CX("<li><a href='%R/md_rules'>Markdown format</a></li>");
1220
+ CX("<li>Plain-text pages use no special formatting.</li>");
12321221
CX("</ul>");
1233
- CX("<hr>Attachments:");
1222
+ CX("<hr><h3>Attachments</h3>");
12341223
CX("<div id='wikiedit-attachments'></div>"
12351224
/* Filled out by JS */);
1236
- CX("</div>");
1225
+ CX("<hr><h3>The \"Sandbox\" Page</h3>");
1226
+ CX("<p>The page named \"sandbox\" is not a real wiki page. "
1227
+ "It provides a place where users may test out wiki syntax "
1228
+ "without having to actually save anything, nor pollute "
1229
+ "the repo with endless test runs. Any attempt to save the "
1230
+ "sandbox page will fail.</p>");
1231
+ CX("<hr><h3>Wiki Name Rules</h3>");
1232
+ well_formed_wiki_name_rules();
1233
+ CX("</div>"/*#wikiedit-tab-save*/);
12371234
}
12381235
12391236
style_emit_script_fossil_bootstrap(0);
12401237
append_diff_javascript(1);
12411238
style_emit_script_fetch(0);
12421239
--- src/wiki.c
+++ src/wiki.c
@@ -1131,24 +1131,27 @@
1131 CX("<div id='wikiedit-tab-content' "
1132 "data-tab-parent='wikiedit-tabs' "
1133 "data-tab-label='Wiki Editor'"
1134 ">");
1135 CX("<div class='flex-container flex-row child-gap-small'>");
 
 
1136 mimetype_option_menu(0);
1137 CX("<button class='wikiedit-content-reload' "
1138 "title='Reload the file from the server, discarding "
1139 "any local edits. To help avoid accidental loss of "
1140 "edits, it requires confirmation (a second click) within "
1141 "a few seconds or it will not reload.'"
1142 ">Discard &amp; Reload</button>");
1143 style_select_list_int("select-font-size",
1144 "editor_font_size", "Editor font size",
1145 NULL/*tooltip*/,
1146 100,
1147 "100%", 100, "125%", 125,
1148 "150%", 150, "175%", 175,
1149 "200%", 200, NULL);
 
 
 
 
 
 
1150 CX("</div>");
1151 CX("<div class='flex-container flex-column stretch'>");
1152 CX("<textarea name='content' id='wikiedit-content-editor' "
1153 "class='wikiedit' "
1154 "rows='25' cols='80'>");
@@ -1194,25 +1197,10 @@
1194
1195 CX("<div class='wikiedit-options flex-container flex-row' "
1196 "id='wikiedit-tab-diff-buttons'>");
1197 CX("<button class='sbs'>Side-by-side</button>"
1198 "<button class='unified'>Unified</button>");
1199 if(0){
1200 /* For the time being let's just ignore all whitespace
1201 ** changes, as files with Windows-style EOLs always show
1202 ** more diffs than we want then they're submitted to
1203 ** ?ajax=diff because JS normalizes them to Unix EOLs.
1204 ** We can revisit this decision later. */
1205 style_select_list_int("diff-ws-policy",
1206 "diff_ws", "Whitespace",
1207 "Whitespace handling policy.",
1208 2,
1209 "Diff all whitespace", 0,
1210 "Ignore EOL whitespace", 1,
1211 "Ignore all whitespace", 2,
1212 NULL);
1213 }
1214 CX("</div>");
1215 CX("<div id='wikiedit-tab-diff-wrapper'>"
1216 "Diffs will be shown here."
1217 "</div>");
1218 CX("</div>"/*#wikiedit-tab-diff*/);
@@ -1219,23 +1207,32 @@
1219 }
1220
1221 {
1222 CX("<div id='wikiedit-tab-save' "
1223 "data-tab-parent='wikiedit-tabs' "
1224 "data-tab-label='Save, etc.'"
1225 ">");
1226 CX("<button class='wikiedit-save'>Save</button>");
1227 CX("<hr>");
1228 CX("Wiki formatting rules:");
1229 CX("<ul>");
1230 CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>");
1231 CX("<li><a href='%R/md_rules'>Markdown format</a></li>");
 
1232 CX("</ul>");
1233 CX("<hr>Attachments:");
1234 CX("<div id='wikiedit-attachments'></div>"
1235 /* Filled out by JS */);
1236 CX("</div>");
 
 
 
 
 
 
 
 
1237 }
1238
1239 style_emit_script_fossil_bootstrap(0);
1240 append_diff_javascript(1);
1241 style_emit_script_fetch(0);
1242
--- src/wiki.c
+++ src/wiki.c
@@ -1131,24 +1131,27 @@
1131 CX("<div id='wikiedit-tab-content' "
1132 "data-tab-parent='wikiedit-tabs' "
1133 "data-tab-label='Wiki Editor'"
1134 ">");
1135 CX("<div class='flex-container flex-row child-gap-small'>");
1136 CX("<span class='input-with-label'>"
1137 "<label>Mime type</label>");
1138 mimetype_option_menu(0);
1139 CX("</span>");
 
 
 
 
 
1140 style_select_list_int("select-font-size",
1141 "editor_font_size", "Editor font size",
1142 NULL/*tooltip*/,
1143 100,
1144 "100%", 100, "125%", 125,
1145 "150%", 150, "175%", 175,
1146 "200%", 200, NULL);
1147 CX("<button class='wikiedit-content-reload' "
1148 "title='Reload the file from the server, discarding "
1149 "any local edits. To help avoid accidental loss of "
1150 "edits, it requires confirmation (a second click) within "
1151 "a few seconds or it will not reload.'"
1152 ">Discard &amp; Reload</button>");
1153 CX("</div>");
1154 CX("<div class='flex-container flex-column stretch'>");
1155 CX("<textarea name='content' id='wikiedit-content-editor' "
1156 "class='wikiedit' "
1157 "rows='25' cols='80'>");
@@ -1194,25 +1197,10 @@
1197
1198 CX("<div class='wikiedit-options flex-container flex-row' "
1199 "id='wikiedit-tab-diff-buttons'>");
1200 CX("<button class='sbs'>Side-by-side</button>"
1201 "<button class='unified'>Unified</button>");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1202 CX("</div>");
1203 CX("<div id='wikiedit-tab-diff-wrapper'>"
1204 "Diffs will be shown here."
1205 "</div>");
1206 CX("</div>"/*#wikiedit-tab-diff*/);
@@ -1219,23 +1207,32 @@
1207 }
1208
1209 {
1210 CX("<div id='wikiedit-tab-save' "
1211 "data-tab-parent='wikiedit-tabs' "
1212 "data-tab-label='Save, Help, etc.'"
1213 ">");
1214 CX("<button class='wikiedit-save'>Save</button>");
1215 CX("<hr>");
1216 CX("<h3>Wiki formatting rules</h3>");
1217 CX("<ul>");
1218 CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>");
1219 CX("<li><a href='%R/md_rules'>Markdown format</a></li>");
1220 CX("<li>Plain-text pages use no special formatting.</li>");
1221 CX("</ul>");
1222 CX("<hr><h3>Attachments</h3>");
1223 CX("<div id='wikiedit-attachments'></div>"
1224 /* Filled out by JS */);
1225 CX("<hr><h3>The \"Sandbox\" Page</h3>");
1226 CX("<p>The page named \"sandbox\" is not a real wiki page. "
1227 "It provides a place where users may test out wiki syntax "
1228 "without having to actually save anything, nor pollute "
1229 "the repo with endless test runs. Any attempt to save the "
1230 "sandbox page will fail.</p>");
1231 CX("<hr><h3>Wiki Name Rules</h3>");
1232 well_formed_wiki_name_rules();
1233 CX("</div>"/*#wikiedit-tab-save*/);
1234 }
1235
1236 style_emit_script_fossil_bootstrap(0);
1237 append_diff_javascript(1);
1238 style_emit_script_fetch(0);
1239

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button