Fossil SCM

/wikiedit and /fileedit can now dynamically load more diff context. Discovered that the unified diff scrolling under the new diff model is not quite right and the magical CSS invocations to solve it properly elude me: for now they're scrolling (if needed) from a higher parent container level of the DOM than they should be, so the line numbers scroll along with the code.

stephan 2021-09-14 09:04 trunk
Commit ef69044cf97e21e7c827222468bf20114d7646f1fa49e12930801ff1ac963e88
+8 -1
--- src/ajax.c
+++ src/ajax.c
@@ -149,16 +149,23 @@
149149
150150
/*
151151
** Renders diffs for ajax routes. pOrig is the "original" (v1) content
152152
** and pContent is the locally-edited (v2) content. diffFlags is any
153153
** set of flags suitable for passing to text_diff().
154
+**
155
+** zOrigHash, if not NULL, must be the SCM-side hash of pOrig's
156
+** contents. If set, additional information may be built into
157
+** the diff output to enable dynamic loading of additional
158
+** diff context.
154159
*/
155
-void ajax_render_diff(Blob * pOrig, Blob *pContent, u64 diffFlags){
160
+void ajax_render_diff(Blob * pOrig, const char * zOrigHash,
161
+ Blob *pContent, u64 diffFlags){
156162
Blob out = empty_blob;
157163
DiffConfig DCfg;
158164
159165
diff_config_init(&DCfg, diffFlags);
166
+ DCfg.zLeftHash = zOrigHash;
160167
text_diff(pOrig, pContent, &out, &DCfg);
161168
if(blob_size(&out)==0){
162169
/* nothing to do */
163170
}else{
164171
CX("%b",&out);
165172
--- src/ajax.c
+++ src/ajax.c
@@ -149,16 +149,23 @@
149
150 /*
151 ** Renders diffs for ajax routes. pOrig is the "original" (v1) content
152 ** and pContent is the locally-edited (v2) content. diffFlags is any
153 ** set of flags suitable for passing to text_diff().
 
 
 
 
 
154 */
155 void ajax_render_diff(Blob * pOrig, Blob *pContent, u64 diffFlags){
 
156 Blob out = empty_blob;
157 DiffConfig DCfg;
158
159 diff_config_init(&DCfg, diffFlags);
 
160 text_diff(pOrig, pContent, &out, &DCfg);
161 if(blob_size(&out)==0){
162 /* nothing to do */
163 }else{
164 CX("%b",&out);
165
--- src/ajax.c
+++ src/ajax.c
@@ -149,16 +149,23 @@
149
150 /*
151 ** Renders diffs for ajax routes. pOrig is the "original" (v1) content
152 ** and pContent is the locally-edited (v2) content. diffFlags is any
153 ** set of flags suitable for passing to text_diff().
154 **
155 ** zOrigHash, if not NULL, must be the SCM-side hash of pOrig's
156 ** contents. If set, additional information may be built into
157 ** the diff output to enable dynamic loading of additional
158 ** diff context.
159 */
160 void ajax_render_diff(Blob * pOrig, const char * zOrigHash,
161 Blob *pContent, u64 diffFlags){
162 Blob out = empty_blob;
163 DiffConfig DCfg;
164
165 diff_config_init(&DCfg, diffFlags);
166 DCfg.zLeftHash = zOrigHash;
167 text_diff(pOrig, pContent, &out, &DCfg);
168 if(blob_size(&out)==0){
169 /* nothing to do */
170 }else{
171 CX("%b",&out);
172
+3 -1
--- src/fileedit.c
+++ src/fileedit.c
@@ -1144,12 +1144,14 @@
11441144
}
11451145
cgi_set_content_type("text/html");
11461146
blob_init(&content, zContent, -1);
11471147
{
11481148
Blob orig = empty_blob;
1149
+ char * const zOrigUuid = rid_to_uuid(frid);
11491150
content_get(frid, &orig);
1150
- ajax_render_diff(&orig, &content, diffFlags);
1151
+ ajax_render_diff(&orig, zOrigUuid, &content, diffFlags);
1152
+ fossil_free(zOrigUuid);
11511153
blob_reset(&orig);
11521154
}
11531155
fossil_free(zRevUuid);
11541156
blob_reset(&content);
11551157
}
11561158
--- src/fileedit.c
+++ src/fileedit.c
@@ -1144,12 +1144,14 @@
1144 }
1145 cgi_set_content_type("text/html");
1146 blob_init(&content, zContent, -1);
1147 {
1148 Blob orig = empty_blob;
 
1149 content_get(frid, &orig);
1150 ajax_render_diff(&orig, &content, diffFlags);
 
1151 blob_reset(&orig);
1152 }
1153 fossil_free(zRevUuid);
1154 blob_reset(&content);
1155 }
1156
--- src/fileedit.c
+++ src/fileedit.c
@@ -1144,12 +1144,14 @@
1144 }
1145 cgi_set_content_type("text/html");
1146 blob_init(&content, zContent, -1);
1147 {
1148 Blob orig = empty_blob;
1149 char * const zOrigUuid = rid_to_uuid(frid);
1150 content_get(frid, &orig);
1151 ajax_render_diff(&orig, zOrigUuid, &content, diffFlags);
1152 fossil_free(zOrigUuid);
1153 blob_reset(&orig);
1154 }
1155 fossil_free(zRevUuid);
1156 blob_reset(&content);
1157 }
1158
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -775,10 +775,11 @@
775775
}, false);
776776
}
777777
return this;
778778
}
779779
window.fossil.page.tweakSbsDiffs = function(){
780
- document.querySelectorAll('table.splitdiff').forEach((e)=>Diff.initTableDiff);
780
+ document.querySelectorAll('table.splitdiff').forEach((e)=>Diff.initTableDiff(e));
781
+ Diff.checkTableWidth();
781782
};
782783
Diff.initTableDiff().checkTableWidth();
783784
window.addEventListener('resize', ()=>Diff.checkTableWidth());
784785
}, false);
785786
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -775,10 +775,11 @@
775 }, false);
776 }
777 return this;
778 }
779 window.fossil.page.tweakSbsDiffs = function(){
780 document.querySelectorAll('table.splitdiff').forEach((e)=>Diff.initTableDiff);
 
781 };
782 Diff.initTableDiff().checkTableWidth();
783 window.addEventListener('resize', ()=>Diff.checkTableWidth());
784 }, false);
785
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -775,10 +775,11 @@
775 }, false);
776 }
777 return this;
778 }
779 window.fossil.page.tweakSbsDiffs = function(){
780 document.querySelectorAll('table.splitdiff').forEach((e)=>Diff.initTableDiff(e));
781 Diff.checkTableWidth();
782 };
783 Diff.initTableDiff().checkTableWidth();
784 window.addEventListener('resize', ()=>Diff.checkTableWidth());
785 }, false);
786
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1201,29 +1201,10 @@
12011201
}
12021202
});
12031203
return this;
12041204
};
12051205
1206
- /**
1207
- Undo some of the SBS diff-rendering bits which hurt us more than
1208
- they help...
1209
- */
1210
- P.tweakSbsDiffs2 = function(){
1211
- if(1){
1212
- const dt = this.e.diffTarget;
1213
- dt.querySelectorAll('.sbsdiffcols .difftxtcol').forEach(
1214
- (dtc)=>{
1215
- const pre = dtc.querySelector('pre');
1216
- pre.style.width = 'initial';
1217
- //pre.removeAttribute('style');
1218
- //console.debug("pre width =",pre.style.width);
1219
- }
1220
- );
1221
- }
1222
- this.tweakSbsDiffs();
1223
- };
1224
-
12251206
/**
12261207
Fetches the content diff based on the contents and settings of
12271208
this page's input fields, and updates the UI with the diff view.
12281209
12291210
Returns this object, noting that the operation is async.
@@ -1248,11 +1229,12 @@
12481229
"<div>Diff <code>[",
12491230
self.finfo.checkin,
12501231
"]</code> &rarr; Local Edits</div>",
12511232
c||'No changes.'
12521233
].join(''));
1253
- if(sbs) P.tweakSbsDiffs2();
1234
+ F.diff.setupDiffContextLoad();
1235
+ if(sbs) P.tweakSbsDiffs();
12541236
F.message('Updated diff.');
12551237
self.tabs.switchToTab(self.e.tabs.diff);
12561238
}
12571239
});
12581240
return this;
12591241
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1201,29 +1201,10 @@
1201 }
1202 });
1203 return this;
1204 };
1205
1206 /**
1207 Undo some of the SBS diff-rendering bits which hurt us more than
1208 they help...
1209 */
1210 P.tweakSbsDiffs2 = function(){
1211 if(1){
1212 const dt = this.e.diffTarget;
1213 dt.querySelectorAll('.sbsdiffcols .difftxtcol').forEach(
1214 (dtc)=>{
1215 const pre = dtc.querySelector('pre');
1216 pre.style.width = 'initial';
1217 //pre.removeAttribute('style');
1218 //console.debug("pre width =",pre.style.width);
1219 }
1220 );
1221 }
1222 this.tweakSbsDiffs();
1223 };
1224
1225 /**
1226 Fetches the content diff based on the contents and settings of
1227 this page's input fields, and updates the UI with the diff view.
1228
1229 Returns this object, noting that the operation is async.
@@ -1248,11 +1229,12 @@
1248 "<div>Diff <code>[",
1249 self.finfo.checkin,
1250 "]</code> &rarr; Local Edits</div>",
1251 c||'No changes.'
1252 ].join(''));
1253 if(sbs) P.tweakSbsDiffs2();
 
1254 F.message('Updated diff.');
1255 self.tabs.switchToTab(self.e.tabs.diff);
1256 }
1257 });
1258 return this;
1259
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -1201,29 +1201,10 @@
1201 }
1202 });
1203 return this;
1204 };
1205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1206 /**
1207 Fetches the content diff based on the contents and settings of
1208 this page's input fields, and updates the UI with the diff view.
1209
1210 Returns this object, noting that the operation is async.
@@ -1248,11 +1229,12 @@
1229 "<div>Diff <code>[",
1230 self.finfo.checkin,
1231 "]</code> &rarr; Local Edits</div>",
1232 c||'No changes.'
1233 ].join(''));
1234 F.diff.setupDiffContextLoad();
1235 if(sbs) P.tweakSbsDiffs();
1236 F.message('Updated diff.');
1237 self.tabs.switchToTab(self.e.tabs.diff);
1238 }
1239 });
1240 return this;
1241
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -1496,30 +1496,10 @@
14961496
}
14971497
});
14981498
return this;
14991499
};
15001500
1501
- /**
1502
- Undo some of the SBS diff-rendering bits which hurt us more than
1503
- they help...
1504
- */
1505
- P.tweakSbsDiffs2 = function(){
1506
- if(1){
1507
- const dt = this.e.diffTarget;
1508
- dt.querySelectorAll('.sbsdiffcols .difftxtcol').forEach(
1509
- (dtc)=>{
1510
- const pre = dtc.querySelector('pre');
1511
- pre.style.width = 'initial';
1512
- //pre.removeAttribute('style');
1513
- //console.debug("pre width =",pre.style.width);
1514
- }
1515
- );
1516
- }
1517
- F.diff.setupDiffContextLoad();
1518
- this.tweakSbsDiffs();
1519
- };
1520
-
15211501
/**
15221502
Fetches the content diff based on the contents and settings of
15231503
this page's input fields, and updates the UI with the diff view.
15241504
15251505
Returns this object, noting that the operation is async.
@@ -1543,11 +1523,12 @@
15431523
"<div>Diff <code>[",
15441524
self.winfo.name,
15451525
"]</code> &rarr; Local Edits</div>",
15461526
c||'No changes.'
15471527
].join(''));
1548
- if(sbs) P.tweakSbsDiffs2();
1528
+ F.diff.setupDiffContextLoad();
1529
+ if(sbs) P.tweakSbsDiffs();
15491530
F.message('Updated diff.');
15501531
self.tabs.switchToTab(self.e.tabs.diff);
15511532
}
15521533
});
15531534
return this;
15541535
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -1496,30 +1496,10 @@
1496 }
1497 });
1498 return this;
1499 };
1500
1501 /**
1502 Undo some of the SBS diff-rendering bits which hurt us more than
1503 they help...
1504 */
1505 P.tweakSbsDiffs2 = function(){
1506 if(1){
1507 const dt = this.e.diffTarget;
1508 dt.querySelectorAll('.sbsdiffcols .difftxtcol').forEach(
1509 (dtc)=>{
1510 const pre = dtc.querySelector('pre');
1511 pre.style.width = 'initial';
1512 //pre.removeAttribute('style');
1513 //console.debug("pre width =",pre.style.width);
1514 }
1515 );
1516 }
1517 F.diff.setupDiffContextLoad();
1518 this.tweakSbsDiffs();
1519 };
1520
1521 /**
1522 Fetches the content diff based on the contents and settings of
1523 this page's input fields, and updates the UI with the diff view.
1524
1525 Returns this object, noting that the operation is async.
@@ -1543,11 +1523,12 @@
1543 "<div>Diff <code>[",
1544 self.winfo.name,
1545 "]</code> &rarr; Local Edits</div>",
1546 c||'No changes.'
1547 ].join(''));
1548 if(sbs) P.tweakSbsDiffs2();
 
1549 F.message('Updated diff.');
1550 self.tabs.switchToTab(self.e.tabs.diff);
1551 }
1552 });
1553 return this;
1554
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -1496,30 +1496,10 @@
1496 }
1497 });
1498 return this;
1499 };
1500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1501 /**
1502 Fetches the content diff based on the contents and settings of
1503 this page's input fields, and updates the UI with the diff view.
1504
1505 Returns this object, noting that the operation is async.
@@ -1543,11 +1523,12 @@
1523 "<div>Diff <code>[",
1524 self.winfo.name,
1525 "]</code> &rarr; Local Edits</div>",
1526 c||'No changes.'
1527 ].join(''));
1528 F.diff.setupDiffContextLoad();
1529 if(sbs) P.tweakSbsDiffs();
1530 F.message('Updated diff.');
1531 self.tabs.switchToTab(self.e.tabs.diff);
1532 }
1533 });
1534 return this;
1535
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -162,17 +162,21 @@
162162
flex-direction: column;
163163
}
164164
body.fileedit #fileedit-tab-diff-wrapper {
165165
margin: 0;
166166
padding: 0;
167
- overflow: auto;
168
- display: flex;
167
+ /*overflow: hidden;*/
168
+ /* ^^^ we "really" want hidden and let a sub-sub-child element
169
+ handle that, but that isn't working, for unknown reasons. */
170
+ overflow-x: auto;
171
+ /*display: flex;
169172
flex-direction: column;
170
- align-items: stretch;
173
+ align-items: stretch;*/
171174
}
172175
body.fileedit #fileedit-tab-diff-wrapper > div {
173176
margin: 0.5em 0 0.5em 0;
177
+ overflow-wrap: break-word;
174178
}
175179
body.fileedit table.sbsdiffcols {
176180
/*width: initial;*/
177181
}
178182
body.fileedit #fileedit-tab-diff-wrapper > pre.udiff {
179183
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -162,17 +162,21 @@
162 flex-direction: column;
163 }
164 body.fileedit #fileedit-tab-diff-wrapper {
165 margin: 0;
166 padding: 0;
167 overflow: auto;
168 display: flex;
 
 
 
169 flex-direction: column;
170 align-items: stretch;
171 }
172 body.fileedit #fileedit-tab-diff-wrapper > div {
173 margin: 0.5em 0 0.5em 0;
 
174 }
175 body.fileedit table.sbsdiffcols {
176 /*width: initial;*/
177 }
178 body.fileedit #fileedit-tab-diff-wrapper > pre.udiff {
179
--- src/style.fileedit.css
+++ src/style.fileedit.css
@@ -162,17 +162,21 @@
162 flex-direction: column;
163 }
164 body.fileedit #fileedit-tab-diff-wrapper {
165 margin: 0;
166 padding: 0;
167 /*overflow: hidden;*/
168 /* ^^^ we "really" want hidden and let a sub-sub-child element
169 handle that, but that isn't working, for unknown reasons. */
170 overflow-x: auto;
171 /*display: flex;
172 flex-direction: column;
173 align-items: stretch;*/
174 }
175 body.fileedit #fileedit-tab-diff-wrapper > div {
176 margin: 0.5em 0 0.5em 0;
177 overflow-wrap: break-word;
178 }
179 body.fileedit table.sbsdiffcols {
180 /*width: initial;*/
181 }
182 body.fileedit #fileedit-tab-diff-wrapper > pre.udiff {
183
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -20,10 +20,16 @@
2020
body.wikiedit #wikiedit-tabs {
2121
margin: 0.5em 0 0 0;
2222
}
2323
body.wikiedit #wikiedit-tab-preview-wrapper {
2424
overflow: auto;
25
+}
26
+body.wikiedit #wikiedit-tab-diff-wrapper {
27
+ /*overflow: hidden;*/
28
+ /* ^^^ we "really" want hidden and let a sub-sub-child element
29
+ handle that, but that isn't working, for unknown reasons. */
30
+ overflow-x: auto;
2531
}
2632
body.wikiedit .tab-container > .tabs > .tab-panel > .wikiedit-options {
2733
margin-top: 0;
2834
border: none;
2935
border-radius: 0;
3036
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -20,10 +20,16 @@
20 body.wikiedit #wikiedit-tabs {
21 margin: 0.5em 0 0 0;
22 }
23 body.wikiedit #wikiedit-tab-preview-wrapper {
24 overflow: auto;
 
 
 
 
 
 
25 }
26 body.wikiedit .tab-container > .tabs > .tab-panel > .wikiedit-options {
27 margin-top: 0;
28 border: none;
29 border-radius: 0;
30
--- src/style.wikiedit.css
+++ src/style.wikiedit.css
@@ -20,10 +20,16 @@
20 body.wikiedit #wikiedit-tabs {
21 margin: 0.5em 0 0 0;
22 }
23 body.wikiedit #wikiedit-tab-preview-wrapper {
24 overflow: auto;
25 }
26 body.wikiedit #wikiedit-tab-diff-wrapper {
27 /*overflow: hidden;*/
28 /* ^^^ we "really" want hidden and let a sub-sub-child element
29 handle that, but that isn't working, for unknown reasons. */
30 overflow-x: auto;
31 }
32 body.wikiedit .tab-container > .tabs > .tab-panel > .wikiedit-options {
33 margin-top: 0;
34 border: none;
35 border-radius: 0;
36
+6 -1
--- src/wiki.c
+++ src/wiki.c
@@ -1062,10 +1062,11 @@
10621062
const char * zPageName = P("page");
10631063
Blob contentNew = empty_blob, contentOrig = empty_blob;
10641064
Manifest * pParent = 0;
10651065
const char * zContent = P("content");
10661066
u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG | DIFF_STRIP_EOLCR;
1067
+ char * zParentUuid = 0;
10671068
10681069
if( zPageName==0 || zPageName[0]==0 ){
10691070
ajax_route_error(400,"Missing page name.");
10701071
return;
10711072
}else if(!wiki_ajax_can_write(zPageName, 0)){
@@ -1079,20 +1080,24 @@
10791080
case 1: diffFlags |= DIFF_IGNORE_EOLWS; break;
10801081
case 2: diffFlags |= DIFF_IGNORE_ALLWS; break;
10811082
default: break;
10821083
}
10831084
wiki_fetch_by_name( zPageName, 0, 0, &pParent );
1085
+ if( pParent ){
1086
+ zParentUuid = rid_to_uuid(pParent->rid);
1087
+ }
10841088
if( pParent && pParent->zWiki && *pParent->zWiki ){
10851089
blob_init(&contentOrig, pParent->zWiki, -1);
10861090
}else{
10871091
blob_init(&contentOrig, "", 0);
10881092
}
10891093
blob_init(&contentNew, zContent ? zContent : "", -1);
10901094
cgi_set_content_type("text/html");
1091
- ajax_render_diff(&contentOrig, &contentNew, diffFlags);
1095
+ ajax_render_diff(&contentOrig, zParentUuid, &contentNew, diffFlags);
10921096
blob_reset(&contentNew);
10931097
blob_reset(&contentOrig);
1098
+ fossil_free(zParentUuid);
10941099
manifest_destroy(pParent);
10951100
}
10961101
10971102
/*
10981103
** Ajax route handler for /wikiajax/preview.
10991104
--- src/wiki.c
+++ src/wiki.c
@@ -1062,10 +1062,11 @@
1062 const char * zPageName = P("page");
1063 Blob contentNew = empty_blob, contentOrig = empty_blob;
1064 Manifest * pParent = 0;
1065 const char * zContent = P("content");
1066 u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG | DIFF_STRIP_EOLCR;
 
1067
1068 if( zPageName==0 || zPageName[0]==0 ){
1069 ajax_route_error(400,"Missing page name.");
1070 return;
1071 }else if(!wiki_ajax_can_write(zPageName, 0)){
@@ -1079,20 +1080,24 @@
1079 case 1: diffFlags |= DIFF_IGNORE_EOLWS; break;
1080 case 2: diffFlags |= DIFF_IGNORE_ALLWS; break;
1081 default: break;
1082 }
1083 wiki_fetch_by_name( zPageName, 0, 0, &pParent );
 
 
 
1084 if( pParent && pParent->zWiki && *pParent->zWiki ){
1085 blob_init(&contentOrig, pParent->zWiki, -1);
1086 }else{
1087 blob_init(&contentOrig, "", 0);
1088 }
1089 blob_init(&contentNew, zContent ? zContent : "", -1);
1090 cgi_set_content_type("text/html");
1091 ajax_render_diff(&contentOrig, &contentNew, diffFlags);
1092 blob_reset(&contentNew);
1093 blob_reset(&contentOrig);
 
1094 manifest_destroy(pParent);
1095 }
1096
1097 /*
1098 ** Ajax route handler for /wikiajax/preview.
1099
--- src/wiki.c
+++ src/wiki.c
@@ -1062,10 +1062,11 @@
1062 const char * zPageName = P("page");
1063 Blob contentNew = empty_blob, contentOrig = empty_blob;
1064 Manifest * pParent = 0;
1065 const char * zContent = P("content");
1066 u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG | DIFF_STRIP_EOLCR;
1067 char * zParentUuid = 0;
1068
1069 if( zPageName==0 || zPageName[0]==0 ){
1070 ajax_route_error(400,"Missing page name.");
1071 return;
1072 }else if(!wiki_ajax_can_write(zPageName, 0)){
@@ -1079,20 +1080,24 @@
1080 case 1: diffFlags |= DIFF_IGNORE_EOLWS; break;
1081 case 2: diffFlags |= DIFF_IGNORE_ALLWS; break;
1082 default: break;
1083 }
1084 wiki_fetch_by_name( zPageName, 0, 0, &pParent );
1085 if( pParent ){
1086 zParentUuid = rid_to_uuid(pParent->rid);
1087 }
1088 if( pParent && pParent->zWiki && *pParent->zWiki ){
1089 blob_init(&contentOrig, pParent->zWiki, -1);
1090 }else{
1091 blob_init(&contentOrig, "", 0);
1092 }
1093 blob_init(&contentNew, zContent ? zContent : "", -1);
1094 cgi_set_content_type("text/html");
1095 ajax_render_diff(&contentOrig, zParentUuid, &contentNew, diffFlags);
1096 blob_reset(&contentNew);
1097 blob_reset(&contentOrig);
1098 fossil_free(zParentUuid);
1099 manifest_destroy(pParent);
1100 }
1101
1102 /*
1103 ** Ajax route handler for /wikiajax/preview.
1104

Keyboard Shortcuts

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