Fossil SCM

Add the ability to have attachments on technotes. Add command-line support for technotes in the "fossil wiki" command.

drh 2015-12-31 16:06 trunk merge
Commit 045deb27ce9f959fa8cc8ed20d9359eb2bb15223
+86 -22
--- src/attach.c
+++ src/attach.c
@@ -26,28 +26,36 @@
2626
** List attachments.
2727
**
2828
** tkt=TICKETUUID
2929
** page=WIKIPAGE
3030
**
31
-** Either one of tkt= or page= are supplied or neither but not both.
32
-** If neither are given, all attachments are listed. If one is given,
33
-** only attachments for the designated ticket or wiki page are shown.
34
-** TICKETUUID must be complete
31
+** At most one of technote=, tkt= or page= are supplied.
32
+** If none is given, all attachments are listed. If one is given,
33
+** only attachments for the designated technote, ticket or wiki page
34
+** are shown. TECHNOTEUUID and TICKETUUID may be just a prefix of the
35
+** relevant tech note or ticket, in which case all attachments of all
36
+** tech notes or tickets with the prefix will be listed.
3537
*/
3638
void attachlist_page(void){
3739
const char *zPage = P("page");
3840
const char *zTkt = P("tkt");
41
+ const char *zTechNote = P("technote");
3942
Blob sql;
4043
Stmt q;
4144
4245
if( zPage && zTkt ) zTkt = 0;
4346
login_check_credentials();
4447
blob_zero(&sql);
4548
blob_append_sql(&sql,
4649
"SELECT datetime(mtime,toLocal()), src, target, filename,"
4750
" comment, user,"
48
- " (SELECT uuid FROM blob WHERE rid=attachid), attachid"
51
+ " (SELECT uuid FROM blob WHERE rid=attachid), attachid,"
52
+ " (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)"
53
+ " THEN 1"
54
+ " WHEN 'event-'||target IN (SELECT tagname FROM tag)"
55
+ " THEN 2"
56
+ " ELSE 0 END)"
4957
" FROM attachment"
5058
);
5159
if( zPage ){
5260
if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
5361
style_header("Attachments To %h", zPage);
@@ -54,10 +62,15 @@
5462
blob_append_sql(&sql, " WHERE target=%Q", zPage);
5563
}else if( zTkt ){
5664
if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
5765
style_header("Attachments To Ticket %S", zTkt);
5866
blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
67
+ }else if( zTechNote ){
68
+ if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
69
+ style_header("Attachments to Tech Note %S", zTechNote);
70
+ blob_append_sql(&sql, " WHERE target GLOB '%q*'",
71
+ zTechNote);
5972
}else{
6073
if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){
6174
login_needed(g.anon.RdTkt || g.anon.RdWiki);
6275
return;
6376
}
@@ -73,21 +86,25 @@
7386
const char *zFilename = db_column_text(&q, 3);
7487
const char *zComment = db_column_text(&q, 4);
7588
const char *zUser = db_column_text(&q, 5);
7689
const char *zUuid = db_column_text(&q, 6);
7790
int attachid = db_column_int(&q, 7);
91
+ // type 0 is a wiki page, 1 is a ticket, 2 is a tech note
92
+ int type = db_column_int(&q, 8);
7893
const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
7994
int i;
8095
char *zUrlTail;
8196
for(i=0; zFilename[i]; i++){
8297
if( zFilename[i]=='/' && zFilename[i+1]!=0 ){
8398
zFilename = &zFilename[i+1];
8499
i = -1;
85100
}
86101
}
87
- if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
102
+ if( type==1 ){
88103
zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
104
+ }else if( type==2 ){
105
+ zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename);
89106
}else{
90107
zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
91108
}
92109
@ <li><p>
93110
@ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
@@ -98,19 +115,22 @@
98115
@ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
99116
if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
100117
if( zComment && zComment[0] ){
101118
@ %!W(zComment)<br />
102119
}
103
- if( zPage==0 && zTkt==0 ){
120
+ if( zPage==0 && zTkt==0 && zTechNote==0 ){
104121
if( zSrc==0 || zSrc[0]==0 ){
105122
zSrc = "Deleted from";
106123
}else {
107124
zSrc = "Added to";
108125
}
109
- if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
126
+ if( type==1 ){
110127
@ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
111128
@ %S(zTarget)</a>
129
+ }else if( type==2 ){
130
+ @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)">
131
+ @ %S(zTarget)</a>
112132
}else{
113133
@ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
114134
@ %h(zTarget)</a>
115135
}
116136
}else{
@@ -138,31 +158,35 @@
138158
** Download or display an attachment.
139159
** Query parameters:
140160
**
141161
** tkt=TICKETUUID
142162
** page=WIKIPAGE
163
+** technote=TECHNOTEUUID
143164
** file=FILENAME
144165
** attachid=ID
145166
**
146167
*/
147168
void attachview_page(void){
148169
const char *zPage = P("page");
149170
const char *zTkt = P("tkt");
171
+ const char *zTechNote = P("technote");
150172
const char *zFile = P("file");
151173
const char *zTarget = 0;
152174
int attachid = atoi(PD("attachid","0"));
153175
char *zUUID;
154176
155
- if( zPage && zTkt ) zTkt = 0;
156177
if( zFile==0 ) fossil_redirect_home();
157178
login_check_credentials();
158179
if( zPage ){
159180
if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
160181
zTarget = zPage;
161182
}else if( zTkt ){
162183
if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
163184
zTarget = zTkt;
185
+ }else if( zTechNote ){
186
+ if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
187
+ zTarget = zTechNote;
164188
}else{
165189
fossil_redirect_home();
166190
}
167191
if( attachid>0 ){
168192
zUUID = db_text(0,
@@ -186,18 +210,19 @@
186210
}else if( zUUID[0]=='x' ){
187211
style_header("Missing");
188212
@ Attachment has been deleted
189213
style_footer();
190214
return;
191
- }
192
- g.perm.Read = 1;
193
- cgi_replace_parameter("name",zUUID);
194
- if( fossil_strcmp(g.zPath,"attachview")==0 ){
195
- artifact_page();
196215
}else{
197
- cgi_replace_parameter("m", mimetype_from_name(zFile));
198
- rawartifact_page();
216
+ g.perm.Read = 1;
217
+ cgi_replace_parameter("name",zUUID);
218
+ if( fossil_strcmp(g.zPath,"attachview")==0 ){
219
+ artifact_page();
220
+ }else{
221
+ cgi_replace_parameter("m", mimetype_from_name(zFile));
222
+ rawartifact_page();
223
+ }
199224
}
200225
}
201226
202227
/*
203228
** Save an attachment control artifact into the repository
@@ -228,27 +253,34 @@
228253
** WEBPAGE: attachadd
229254
** Add a new attachment.
230255
**
231256
** tkt=TICKETUUID
232257
** page=WIKIPAGE
258
+** technote=TECHNOTEUUID
233259
** from=URL
234260
**
235261
*/
236262
void attachadd_page(void){
237263
const char *zPage = P("page");
238264
const char *zTkt = P("tkt");
265
+ const char *zTechNote = P("technote");
239266
const char *zFrom = P("from");
240267
const char *aContent = P("f");
241268
const char *zName = PD("f:filename","unknown");
242269
const char *zTarget;
243
- const char *zTargetType;
270
+ char *zTargetType;
244271
int szContent = atoi(PD("f:bytes","0"));
245272
int goodCaptcha = 1;
246273
247274
if( P("cancel") ) cgi_redirect(zFrom);
248
- if( zPage && zTkt ) fossil_redirect_home();
249
- if( zPage==0 && zTkt==0 ) fossil_redirect_home();
275
+ if( (zPage && zTkt)
276
+ || (zPage && zTechNote)
277
+ || (zTkt && zTechNote)
278
+ ){
279
+ fossil_redirect_home();
280
+ }
281
+ if( zPage==0 && zTkt==0 && zTechNote==0) fossil_redirect_home();
250282
login_check_credentials();
251283
if( zPage ){
252284
if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
253285
login_needed(g.anon.ApndWiki && g.anon.Attach);
254286
return;
@@ -257,10 +289,24 @@
257289
fossil_redirect_home();
258290
}
259291
zTarget = zPage;
260292
zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>",
261293
zPage, zPage);
294
+ }else if ( zTechNote ){
295
+ if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){
296
+ login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach);
297
+ return;
298
+ }
299
+ if( !db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'", zTechNote) ){
300
+ zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag"
301
+ " WHERE tagname GLOB 'event-%q*'", zTechNote);
302
+ if( zTechNote==0) fossil_redirect_home();
303
+ }
304
+ zTarget = zTechNote;
305
+ zTargetType = mprintf("Tech Note <a href=\"%R/technote/%h\">%h</a>",
306
+ zTechNote, zTechNote);
307
+
262308
}else{
263309
if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
264310
login_needed(g.anon.ApndTkt && g.anon.Attach);
265311
return;
266312
}
@@ -340,10 +386,12 @@
340386
@ <input type="file" name="f" size="60" /><br />
341387
@ Description:<br />
342388
@ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br />
343389
if( zTkt ){
344390
@ <input type="hidden" name="tkt" value="%h(zTkt)" />
391
+ }else if( zTechNote ){
392
+ @ <input type="hidden" name="technote" value="%h(zTechNote)" />
345393
}else{
346394
@ <input type="hidden" name="page" value="%h(zPage)" />
347395
}
348396
@ <input type="hidden" name="from" value="%h(zFrom)" />
349397
@ <input type="submit" name="ok" value="Add Attachment" />
@@ -350,10 +398,11 @@
350398
@ <input type="submit" name="cancel" value="Cancel" />
351399
@ </div>
352400
captcha_generate(0);
353401
@ </form>
354402
style_footer();
403
+ fossil_free(zTargetType);
355404
}
356405
357406
/*
358407
** WEBPAGE: ainfo
359408
** URL: /ainfo?name=ARTIFACTID
@@ -364,15 +413,16 @@
364413
int rid; /* RID for the control artifact */
365414
int ridSrc; /* RID for the attached file */
366415
char *zDate; /* Date attached */
367416
const char *zUuid; /* UUID of the control artifact */
368417
Manifest *pAttach; /* Parse of the control artifact */
369
- const char *zTarget; /* Wiki or ticket attached to */
418
+ const char *zTarget; /* Wiki, ticket or tech note attached to */
370419
const char *zSrc; /* UUID of the attached file */
371420
const char *zName; /* Name of the attached file */
372421
const char *zDesc; /* Description of the attached file */
373422
const char *zWikiName = 0; /* Wiki page name when attached to Wiki */
423
+ const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */
374424
const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */
375425
int modPending; /* True if awaiting moderation */
376426
const char *zModAction; /* Moderation action or NULL */
377427
int isModerator; /* TRUE if user is the moderator */
378428
const char *zMime; /* MIME Type */
@@ -422,15 +472,23 @@
422472
zWikiName = zTarget;
423473
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
424474
if( g.perm.WrWiki ){
425475
style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
426476
}
477
+ }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){
478
+ zTNUuid = zTarget;
479
+ if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
480
+ if( g.perm.Write && g.perm.WrWiki ){
481
+ style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
482
+ }
427483
}
428484
zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
429485
430486
if( P("confirm")
431
- && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
487
+ && ((zTktUuid && g.perm.WrTkt) ||
488
+ (zWikiName && g.perm.WrWiki) ||
489
+ (zTNUuid && g.perm.Write && g.perm.WrWiki))
432490
){
433491
int i, n, rid;
434492
char *zDate;
435493
Blob manifest;
436494
Blob cksum;
@@ -454,11 +512,13 @@
454512
db_end_transaction(0);
455513
@ <p>The attachment below has been deleted.</p>
456514
}
457515
458516
if( P("del")
459
- && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
517
+ && ((zTktUuid && g.perm.WrTkt) ||
518
+ (zWikiName && g.perm.WrWiki) ||
519
+ (zTNUuid && g.perm.Write && g.perm.WrWiki))
460520
){
461521
form_begin(0, "%R/ainfo/%!S", zUuid);
462522
@ <p>Confirm you want to delete the attachment shown below.
463523
@ <input type="submit" name="confirm" value="Confirm">
464524
@ </form>
@@ -502,10 +562,14 @@
502562
}
503563
if( zTktUuid ){
504564
@ <tr><th>Ticket:</th>
505565
@ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
506566
}
567
+ if( zTNUuid ){
568
+ @ <tr><th>Tech Note:</th>
569
+ @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>
570
+ }
507571
if( zWikiName ){
508572
@ <tr><th>Wiki&nbsp;Page:</th>
509573
@ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
510574
}
511575
@ <tr><th>Date:</th><td>
512576
--- src/attach.c
+++ src/attach.c
@@ -26,28 +26,36 @@
26 ** List attachments.
27 **
28 ** tkt=TICKETUUID
29 ** page=WIKIPAGE
30 **
31 ** Either one of tkt= or page= are supplied or neither but not both.
32 ** If neither are given, all attachments are listed. If one is given,
33 ** only attachments for the designated ticket or wiki page are shown.
34 ** TICKETUUID must be complete
 
 
35 */
36 void attachlist_page(void){
37 const char *zPage = P("page");
38 const char *zTkt = P("tkt");
 
39 Blob sql;
40 Stmt q;
41
42 if( zPage && zTkt ) zTkt = 0;
43 login_check_credentials();
44 blob_zero(&sql);
45 blob_append_sql(&sql,
46 "SELECT datetime(mtime,toLocal()), src, target, filename,"
47 " comment, user,"
48 " (SELECT uuid FROM blob WHERE rid=attachid), attachid"
 
 
 
 
 
49 " FROM attachment"
50 );
51 if( zPage ){
52 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
53 style_header("Attachments To %h", zPage);
@@ -54,10 +62,15 @@
54 blob_append_sql(&sql, " WHERE target=%Q", zPage);
55 }else if( zTkt ){
56 if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
57 style_header("Attachments To Ticket %S", zTkt);
58 blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
 
 
 
 
 
59 }else{
60 if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){
61 login_needed(g.anon.RdTkt || g.anon.RdWiki);
62 return;
63 }
@@ -73,21 +86,25 @@
73 const char *zFilename = db_column_text(&q, 3);
74 const char *zComment = db_column_text(&q, 4);
75 const char *zUser = db_column_text(&q, 5);
76 const char *zUuid = db_column_text(&q, 6);
77 int attachid = db_column_int(&q, 7);
 
 
78 const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
79 int i;
80 char *zUrlTail;
81 for(i=0; zFilename[i]; i++){
82 if( zFilename[i]=='/' && zFilename[i+1]!=0 ){
83 zFilename = &zFilename[i+1];
84 i = -1;
85 }
86 }
87 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
88 zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
 
 
89 }else{
90 zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
91 }
92 @ <li><p>
93 @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
@@ -98,19 +115,22 @@
98 @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
99 if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
100 if( zComment && zComment[0] ){
101 @ %!W(zComment)<br />
102 }
103 if( zPage==0 && zTkt==0 ){
104 if( zSrc==0 || zSrc[0]==0 ){
105 zSrc = "Deleted from";
106 }else {
107 zSrc = "Added to";
108 }
109 if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
110 @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
111 @ %S(zTarget)</a>
 
 
 
112 }else{
113 @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
114 @ %h(zTarget)</a>
115 }
116 }else{
@@ -138,31 +158,35 @@
138 ** Download or display an attachment.
139 ** Query parameters:
140 **
141 ** tkt=TICKETUUID
142 ** page=WIKIPAGE
 
143 ** file=FILENAME
144 ** attachid=ID
145 **
146 */
147 void attachview_page(void){
148 const char *zPage = P("page");
149 const char *zTkt = P("tkt");
 
150 const char *zFile = P("file");
151 const char *zTarget = 0;
152 int attachid = atoi(PD("attachid","0"));
153 char *zUUID;
154
155 if( zPage && zTkt ) zTkt = 0;
156 if( zFile==0 ) fossil_redirect_home();
157 login_check_credentials();
158 if( zPage ){
159 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
160 zTarget = zPage;
161 }else if( zTkt ){
162 if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
163 zTarget = zTkt;
 
 
 
164 }else{
165 fossil_redirect_home();
166 }
167 if( attachid>0 ){
168 zUUID = db_text(0,
@@ -186,18 +210,19 @@
186 }else if( zUUID[0]=='x' ){
187 style_header("Missing");
188 @ Attachment has been deleted
189 style_footer();
190 return;
191 }
192 g.perm.Read = 1;
193 cgi_replace_parameter("name",zUUID);
194 if( fossil_strcmp(g.zPath,"attachview")==0 ){
195 artifact_page();
196 }else{
197 cgi_replace_parameter("m", mimetype_from_name(zFile));
198 rawartifact_page();
 
 
 
 
 
 
199 }
200 }
201
202 /*
203 ** Save an attachment control artifact into the repository
@@ -228,27 +253,34 @@
228 ** WEBPAGE: attachadd
229 ** Add a new attachment.
230 **
231 ** tkt=TICKETUUID
232 ** page=WIKIPAGE
 
233 ** from=URL
234 **
235 */
236 void attachadd_page(void){
237 const char *zPage = P("page");
238 const char *zTkt = P("tkt");
 
239 const char *zFrom = P("from");
240 const char *aContent = P("f");
241 const char *zName = PD("f:filename","unknown");
242 const char *zTarget;
243 const char *zTargetType;
244 int szContent = atoi(PD("f:bytes","0"));
245 int goodCaptcha = 1;
246
247 if( P("cancel") ) cgi_redirect(zFrom);
248 if( zPage && zTkt ) fossil_redirect_home();
249 if( zPage==0 && zTkt==0 ) fossil_redirect_home();
 
 
 
 
 
250 login_check_credentials();
251 if( zPage ){
252 if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
253 login_needed(g.anon.ApndWiki && g.anon.Attach);
254 return;
@@ -257,10 +289,24 @@
257 fossil_redirect_home();
258 }
259 zTarget = zPage;
260 zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>",
261 zPage, zPage);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262 }else{
263 if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
264 login_needed(g.anon.ApndTkt && g.anon.Attach);
265 return;
266 }
@@ -340,10 +386,12 @@
340 @ <input type="file" name="f" size="60" /><br />
341 @ Description:<br />
342 @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br />
343 if( zTkt ){
344 @ <input type="hidden" name="tkt" value="%h(zTkt)" />
 
 
345 }else{
346 @ <input type="hidden" name="page" value="%h(zPage)" />
347 }
348 @ <input type="hidden" name="from" value="%h(zFrom)" />
349 @ <input type="submit" name="ok" value="Add Attachment" />
@@ -350,10 +398,11 @@
350 @ <input type="submit" name="cancel" value="Cancel" />
351 @ </div>
352 captcha_generate(0);
353 @ </form>
354 style_footer();
 
355 }
356
357 /*
358 ** WEBPAGE: ainfo
359 ** URL: /ainfo?name=ARTIFACTID
@@ -364,15 +413,16 @@
364 int rid; /* RID for the control artifact */
365 int ridSrc; /* RID for the attached file */
366 char *zDate; /* Date attached */
367 const char *zUuid; /* UUID of the control artifact */
368 Manifest *pAttach; /* Parse of the control artifact */
369 const char *zTarget; /* Wiki or ticket attached to */
370 const char *zSrc; /* UUID of the attached file */
371 const char *zName; /* Name of the attached file */
372 const char *zDesc; /* Description of the attached file */
373 const char *zWikiName = 0; /* Wiki page name when attached to Wiki */
 
374 const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */
375 int modPending; /* True if awaiting moderation */
376 const char *zModAction; /* Moderation action or NULL */
377 int isModerator; /* TRUE if user is the moderator */
378 const char *zMime; /* MIME Type */
@@ -422,15 +472,23 @@
422 zWikiName = zTarget;
423 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
424 if( g.perm.WrWiki ){
425 style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
426 }
 
 
 
 
 
 
427 }
428 zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
429
430 if( P("confirm")
431 && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
 
 
432 ){
433 int i, n, rid;
434 char *zDate;
435 Blob manifest;
436 Blob cksum;
@@ -454,11 +512,13 @@
454 db_end_transaction(0);
455 @ <p>The attachment below has been deleted.</p>
456 }
457
458 if( P("del")
459 && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
 
 
460 ){
461 form_begin(0, "%R/ainfo/%!S", zUuid);
462 @ <p>Confirm you want to delete the attachment shown below.
463 @ <input type="submit" name="confirm" value="Confirm">
464 @ </form>
@@ -502,10 +562,14 @@
502 }
503 if( zTktUuid ){
504 @ <tr><th>Ticket:</th>
505 @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
506 }
 
 
 
 
507 if( zWikiName ){
508 @ <tr><th>Wiki&nbsp;Page:</th>
509 @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
510 }
511 @ <tr><th>Date:</th><td>
512
--- src/attach.c
+++ src/attach.c
@@ -26,28 +26,36 @@
26 ** List attachments.
27 **
28 ** tkt=TICKETUUID
29 ** page=WIKIPAGE
30 **
31 ** At most one of technote=, tkt= or page= are supplied.
32 ** If none is given, all attachments are listed. If one is given,
33 ** only attachments for the designated technote, ticket or wiki page
34 ** are shown. TECHNOTEUUID and TICKETUUID may be just a prefix of the
35 ** relevant tech note or ticket, in which case all attachments of all
36 ** tech notes or tickets with the prefix will be listed.
37 */
38 void attachlist_page(void){
39 const char *zPage = P("page");
40 const char *zTkt = P("tkt");
41 const char *zTechNote = P("technote");
42 Blob sql;
43 Stmt q;
44
45 if( zPage && zTkt ) zTkt = 0;
46 login_check_credentials();
47 blob_zero(&sql);
48 blob_append_sql(&sql,
49 "SELECT datetime(mtime,toLocal()), src, target, filename,"
50 " comment, user,"
51 " (SELECT uuid FROM blob WHERE rid=attachid), attachid,"
52 " (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)"
53 " THEN 1"
54 " WHEN 'event-'||target IN (SELECT tagname FROM tag)"
55 " THEN 2"
56 " ELSE 0 END)"
57 " FROM attachment"
58 );
59 if( zPage ){
60 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
61 style_header("Attachments To %h", zPage);
@@ -54,10 +62,15 @@
62 blob_append_sql(&sql, " WHERE target=%Q", zPage);
63 }else if( zTkt ){
64 if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
65 style_header("Attachments To Ticket %S", zTkt);
66 blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
67 }else if( zTechNote ){
68 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
69 style_header("Attachments to Tech Note %S", zTechNote);
70 blob_append_sql(&sql, " WHERE target GLOB '%q*'",
71 zTechNote);
72 }else{
73 if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){
74 login_needed(g.anon.RdTkt || g.anon.RdWiki);
75 return;
76 }
@@ -73,21 +86,25 @@
86 const char *zFilename = db_column_text(&q, 3);
87 const char *zComment = db_column_text(&q, 4);
88 const char *zUser = db_column_text(&q, 5);
89 const char *zUuid = db_column_text(&q, 6);
90 int attachid = db_column_int(&q, 7);
91 // type 0 is a wiki page, 1 is a ticket, 2 is a tech note
92 int type = db_column_int(&q, 8);
93 const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
94 int i;
95 char *zUrlTail;
96 for(i=0; zFilename[i]; i++){
97 if( zFilename[i]=='/' && zFilename[i+1]!=0 ){
98 zFilename = &zFilename[i+1];
99 i = -1;
100 }
101 }
102 if( type==1 ){
103 zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
104 }else if( type==2 ){
105 zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename);
106 }else{
107 zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
108 }
109 @ <li><p>
110 @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
@@ -98,19 +115,22 @@
115 @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
116 if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
117 if( zComment && zComment[0] ){
118 @ %!W(zComment)<br />
119 }
120 if( zPage==0 && zTkt==0 && zTechNote==0 ){
121 if( zSrc==0 || zSrc[0]==0 ){
122 zSrc = "Deleted from";
123 }else {
124 zSrc = "Added to";
125 }
126 if( type==1 ){
127 @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
128 @ %S(zTarget)</a>
129 }else if( type==2 ){
130 @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)">
131 @ %S(zTarget)</a>
132 }else{
133 @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
134 @ %h(zTarget)</a>
135 }
136 }else{
@@ -138,31 +158,35 @@
158 ** Download or display an attachment.
159 ** Query parameters:
160 **
161 ** tkt=TICKETUUID
162 ** page=WIKIPAGE
163 ** technote=TECHNOTEUUID
164 ** file=FILENAME
165 ** attachid=ID
166 **
167 */
168 void attachview_page(void){
169 const char *zPage = P("page");
170 const char *zTkt = P("tkt");
171 const char *zTechNote = P("technote");
172 const char *zFile = P("file");
173 const char *zTarget = 0;
174 int attachid = atoi(PD("attachid","0"));
175 char *zUUID;
176
 
177 if( zFile==0 ) fossil_redirect_home();
178 login_check_credentials();
179 if( zPage ){
180 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
181 zTarget = zPage;
182 }else if( zTkt ){
183 if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
184 zTarget = zTkt;
185 }else if( zTechNote ){
186 if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
187 zTarget = zTechNote;
188 }else{
189 fossil_redirect_home();
190 }
191 if( attachid>0 ){
192 zUUID = db_text(0,
@@ -186,18 +210,19 @@
210 }else if( zUUID[0]=='x' ){
211 style_header("Missing");
212 @ Attachment has been deleted
213 style_footer();
214 return;
 
 
 
 
 
215 }else{
216 g.perm.Read = 1;
217 cgi_replace_parameter("name",zUUID);
218 if( fossil_strcmp(g.zPath,"attachview")==0 ){
219 artifact_page();
220 }else{
221 cgi_replace_parameter("m", mimetype_from_name(zFile));
222 rawartifact_page();
223 }
224 }
225 }
226
227 /*
228 ** Save an attachment control artifact into the repository
@@ -228,27 +253,34 @@
253 ** WEBPAGE: attachadd
254 ** Add a new attachment.
255 **
256 ** tkt=TICKETUUID
257 ** page=WIKIPAGE
258 ** technote=TECHNOTEUUID
259 ** from=URL
260 **
261 */
262 void attachadd_page(void){
263 const char *zPage = P("page");
264 const char *zTkt = P("tkt");
265 const char *zTechNote = P("technote");
266 const char *zFrom = P("from");
267 const char *aContent = P("f");
268 const char *zName = PD("f:filename","unknown");
269 const char *zTarget;
270 char *zTargetType;
271 int szContent = atoi(PD("f:bytes","0"));
272 int goodCaptcha = 1;
273
274 if( P("cancel") ) cgi_redirect(zFrom);
275 if( (zPage && zTkt)
276 || (zPage && zTechNote)
277 || (zTkt && zTechNote)
278 ){
279 fossil_redirect_home();
280 }
281 if( zPage==0 && zTkt==0 && zTechNote==0) fossil_redirect_home();
282 login_check_credentials();
283 if( zPage ){
284 if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
285 login_needed(g.anon.ApndWiki && g.anon.Attach);
286 return;
@@ -257,10 +289,24 @@
289 fossil_redirect_home();
290 }
291 zTarget = zPage;
292 zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>",
293 zPage, zPage);
294 }else if ( zTechNote ){
295 if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){
296 login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach);
297 return;
298 }
299 if( !db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'", zTechNote) ){
300 zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag"
301 " WHERE tagname GLOB 'event-%q*'", zTechNote);
302 if( zTechNote==0) fossil_redirect_home();
303 }
304 zTarget = zTechNote;
305 zTargetType = mprintf("Tech Note <a href=\"%R/technote/%h\">%h</a>",
306 zTechNote, zTechNote);
307
308 }else{
309 if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
310 login_needed(g.anon.ApndTkt && g.anon.Attach);
311 return;
312 }
@@ -340,10 +386,12 @@
386 @ <input type="file" name="f" size="60" /><br />
387 @ Description:<br />
388 @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br />
389 if( zTkt ){
390 @ <input type="hidden" name="tkt" value="%h(zTkt)" />
391 }else if( zTechNote ){
392 @ <input type="hidden" name="technote" value="%h(zTechNote)" />
393 }else{
394 @ <input type="hidden" name="page" value="%h(zPage)" />
395 }
396 @ <input type="hidden" name="from" value="%h(zFrom)" />
397 @ <input type="submit" name="ok" value="Add Attachment" />
@@ -350,10 +398,11 @@
398 @ <input type="submit" name="cancel" value="Cancel" />
399 @ </div>
400 captcha_generate(0);
401 @ </form>
402 style_footer();
403 fossil_free(zTargetType);
404 }
405
406 /*
407 ** WEBPAGE: ainfo
408 ** URL: /ainfo?name=ARTIFACTID
@@ -364,15 +413,16 @@
413 int rid; /* RID for the control artifact */
414 int ridSrc; /* RID for the attached file */
415 char *zDate; /* Date attached */
416 const char *zUuid; /* UUID of the control artifact */
417 Manifest *pAttach; /* Parse of the control artifact */
418 const char *zTarget; /* Wiki, ticket or tech note attached to */
419 const char *zSrc; /* UUID of the attached file */
420 const char *zName; /* Name of the attached file */
421 const char *zDesc; /* Description of the attached file */
422 const char *zWikiName = 0; /* Wiki page name when attached to Wiki */
423 const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */
424 const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */
425 int modPending; /* True if awaiting moderation */
426 const char *zModAction; /* Moderation action or NULL */
427 int isModerator; /* TRUE if user is the moderator */
428 const char *zMime; /* MIME Type */
@@ -422,15 +472,23 @@
472 zWikiName = zTarget;
473 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
474 if( g.perm.WrWiki ){
475 style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
476 }
477 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){
478 zTNUuid = zTarget;
479 if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
480 if( g.perm.Write && g.perm.WrWiki ){
481 style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
482 }
483 }
484 zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
485
486 if( P("confirm")
487 && ((zTktUuid && g.perm.WrTkt) ||
488 (zWikiName && g.perm.WrWiki) ||
489 (zTNUuid && g.perm.Write && g.perm.WrWiki))
490 ){
491 int i, n, rid;
492 char *zDate;
493 Blob manifest;
494 Blob cksum;
@@ -454,11 +512,13 @@
512 db_end_transaction(0);
513 @ <p>The attachment below has been deleted.</p>
514 }
515
516 if( P("del")
517 && ((zTktUuid && g.perm.WrTkt) ||
518 (zWikiName && g.perm.WrWiki) ||
519 (zTNUuid && g.perm.Write && g.perm.WrWiki))
520 ){
521 form_begin(0, "%R/ainfo/%!S", zUuid);
522 @ <p>Confirm you want to delete the attachment shown below.
523 @ <input type="submit" name="confirm" value="Confirm">
524 @ </form>
@@ -502,10 +562,14 @@
562 }
563 if( zTktUuid ){
564 @ <tr><th>Ticket:</th>
565 @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
566 }
567 if( zTNUuid ){
568 @ <tr><th>Tech Note:</th>
569 @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>
570 }
571 if( zWikiName ){
572 @ <tr><th>Wiki&nbsp;Page:</th>
573 @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
574 }
575 @ <tr><th>Date:</th><td>
576
+176 -82
--- src/event.c
+++ src/event.c
@@ -62,11 +62,11 @@
6262
** Display an existing event identified by EVENTID
6363
*/
6464
void event_page(void){
6565
int rid = 0; /* rid of the event artifact */
6666
char *zUuid; /* UUID corresponding to rid */
67
- const char *zId; /* Event identifier */
67
+ const char *zId; /* Event identifier */
6868
const char *zVerbose; /* Value of verbose option */
6969
char *zETime; /* Time of the tech-note */
7070
char *zATime; /* Time the artifact was created */
7171
int specRid; /* rid specified by aid= parameter */
7272
int prevRid, nextRid; /* Previous or next edits of this tech-note */
@@ -75,10 +75,11 @@
7575
Blob title; /* Title extracted from the technote body */
7676
Blob tail; /* Event body that comes after the title */
7777
Stmt q1; /* Query to search for the technote */
7878
int verboseFlag; /* True to show details */
7979
const char *zMimetype = 0; /* Mimetype of the document */
80
+ const char *zFullId; /* Full event identifier */
8081
8182
8283
/* wiki-read privilege is needed in order to read tech-notes.
8384
*/
8485
login_check_credentials();
@@ -150,10 +151,15 @@
150151
tail = fullbody;
151152
}
152153
style_header("%s", blob_str(&title));
153154
if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
154155
style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
156
+ if( g.perm.Attach ){
157
+ style_submenu_element("Attach", "Add an attachment",
158
+ "%R/attachadd?technote=%!S&from=%R/technote/%!S",
159
+ zId, zId);
160
+ }
155161
}
156162
zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
157163
style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
158164
if( g.perm.Hyperlink ){
159165
if( verboseFlag ){
@@ -216,13 +222,123 @@
216222
}else{
217223
@ <pre>
218224
@ %h(blob_str(&fullbody))
219225
@ </pre>
220226
}
227
+ zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
228
+ " FROM tag"
229
+ " WHERE tagname GLOB 'event-%q*'",
230
+ zId);
231
+ attachment_list(zFullId, "<hr /><h2>Attachments:</h2><ul>");
221232
style_footer();
222233
manifest_destroy(pTNote);
223234
}
235
+
236
+/*
237
+** Add or update a new tech note to the repository. rid is id of
238
+** the prior version of this technote, if any.
239
+**
240
+** returns 1 if the tech note was added or updated, 0 if the
241
+** update failed making an invalid artifact
242
+*/
243
+int event_commit_common(
244
+ int rid, /* id of the prior version of the technote */
245
+ const char *zId, /* hash label for the technote */
246
+ const char *zBody, /* content of the technote */
247
+ char *zETime, /* timestamp for the technote */
248
+ const char *zMimetype, /* mimetype for the technote N-card */
249
+ const char *zComment, /* comment shown on the timeline */
250
+ const char *zTags, /* tags associated with this technote */
251
+ const char *zClr /* Background color */
252
+){
253
+ Blob event;
254
+ char *zDate;
255
+ Blob cksum;
256
+ int nrid, n;
257
+
258
+ blob_init(&event, 0, 0);
259
+ db_begin_transaction();
260
+ while( fossil_isspace(zComment[0]) ) zComment++;
261
+ n = strlen(zComment);
262
+ while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
263
+ if( n>0 ){
264
+ blob_appendf(&event, "C %#F\n", n, zComment);
265
+ }
266
+ zDate = date_in_standard_format("now");
267
+ blob_appendf(&event, "D %s\n", zDate);
268
+ free(zDate);
269
+
270
+ zETime[10] = 'T';
271
+ blob_appendf(&event, "E %s %s\n", zETime, zId);
272
+ zETime[10] = ' ';
273
+ if( rid ){
274
+ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
275
+ blob_appendf(&event, "P %s\n", zUuid);
276
+ free(zUuid);
277
+ }
278
+ if( zMimetype && zMimetype[0] ){
279
+ blob_appendf(&event, "N %s\n", zMimetype);
280
+ }
281
+ if( zClr && zClr[0] ){
282
+ blob_appendf(&event, "T +bgcolor * %F\n", zClr);
283
+ }
284
+ if( zTags && zTags[0] ){
285
+ Blob tags, one;
286
+ int i, j;
287
+ Stmt q;
288
+ char *zBlob;
289
+
290
+ /* Load the tags string into a blob */
291
+ blob_zero(&tags);
292
+ blob_append(&tags, zTags, -1);
293
+
294
+ /* Collapse all sequences of whitespace and "," characters into
295
+ ** a single space character */
296
+ zBlob = blob_str(&tags);
297
+ for(i=j=0; zBlob[i]; i++, j++){
298
+ if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
299
+ while( fossil_isspace(zBlob[i+1]) ){ i++; }
300
+ zBlob[j] = ' ';
301
+ }else{
302
+ zBlob[j] = zBlob[i];
303
+ }
304
+ }
305
+ blob_resize(&tags, j);
306
+
307
+ /* Parse out each tag and load it into a temporary table for sorting */
308
+ db_multi_exec("CREATE TEMP TABLE newtags(x);");
309
+ while( blob_token(&tags, &one) ){
310
+ db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
311
+ }
312
+ blob_reset(&tags);
313
+
314
+ /* Extract the tags in sorted order and make an entry in the
315
+ ** artifact for each. */
316
+ db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
317
+ while( db_step(&q)==SQLITE_ROW ){
318
+ blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
319
+ }
320
+ db_finalize(&q);
321
+ }
322
+ if( !login_is_nobody() ){
323
+ blob_appendf(&event, "U %F\n", login_name());
324
+ }
325
+ blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
326
+ md5sum_blob(&event, &cksum);
327
+ blob_appendf(&event, "Z %b\n", &cksum);
328
+ blob_reset(&cksum);
329
+ nrid = content_put(&event);
330
+ db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
331
+ if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
332
+ db_end_transaction(1);
333
+ return 0;
334
+ }
335
+ assert( blob_is_reset(&event) );
336
+ content_deltify(rid, nrid, 0);
337
+ db_end_transaction(0);
338
+ return 1;
339
+}
224340
225341
/*
226342
** WEBPAGE: technoteedit
227343
** WEBPAGE: eventedit
228344
**
@@ -323,97 +439,19 @@
323439
);
324440
}
325441
}
326442
zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
327443
if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
328
- char *zDate;
329
- Blob cksum;
330
- int nrid, n;
331
- blob_init(&event, 0, 0);
332
- db_begin_transaction();
333444
login_verify_csrf_secret();
334
- while( fossil_isspace(zComment[0]) ) zComment++;
335
- n = strlen(zComment);
336
- while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
337
- if( n>0 ){
338
- blob_appendf(&event, "C %#F\n", n, zComment);
339
- }
340
- zDate = date_in_standard_format("now");
341
- blob_appendf(&event, "D %s\n", zDate);
342
- free(zDate);
343
- zETime[10] = 'T';
344
- blob_appendf(&event, "E %s %s\n", zETime, zId);
345
- zETime[10] = ' ';
346
- if( rid ){
347
- char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
348
- blob_appendf(&event, "P %s\n", zUuid);
349
- free(zUuid);
350
- }
351
- if( zMimetype && zMimetype[0] ){
352
- blob_appendf(&event, "N %s\n", zMimetype);
353
- }
354
- if( zClr && zClr[0] ){
355
- blob_appendf(&event, "T +bgcolor * %F\n", zClr);
356
- }
357
- if( zTags && zTags[0] ){
358
- Blob tags, one;
359
- int i, j;
360
- Stmt q;
361
- char *zBlob;
362
-
363
- /* Load the tags string into a blob */
364
- blob_zero(&tags);
365
- blob_append(&tags, zTags, -1);
366
-
367
- /* Collapse all sequences of whitespace and "," characters into
368
- ** a single space character */
369
- zBlob = blob_str(&tags);
370
- for(i=j=0; zBlob[i]; i++, j++){
371
- if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
372
- while( fossil_isspace(zBlob[i+1]) ){ i++; }
373
- zBlob[j] = ' ';
374
- }else{
375
- zBlob[j] = zBlob[i];
376
- }
377
- }
378
- blob_resize(&tags, j);
379
-
380
- /* Parse out each tag and load it into a temporary table for sorting */
381
- db_multi_exec("CREATE TEMP TABLE newtags(x);");
382
- while( blob_token(&tags, &one) ){
383
- db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
384
- }
385
- blob_reset(&tags);
386
-
387
- /* Extract the tags in sorted order and make an entry in the
388
- ** artifact for each. */
389
- db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
390
- while( db_step(&q)==SQLITE_ROW ){
391
- blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
392
- }
393
- db_finalize(&q);
394
- }
395
- if( !login_is_nobody() ){
396
- blob_appendf(&event, "U %F\n", login_name());
397
- }
398
- blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
399
- md5sum_blob(&event, &cksum);
400
- blob_appendf(&event, "Z %b\n", &cksum);
401
- blob_reset(&cksum);
402
- nrid = content_put(&event);
403
- db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
404
- if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
405
- db_end_transaction(1);
445
+ if ( !event_commit_common(rid, zId, zBody, zETime,
446
+ zMimetype, zComment, zTags, zClr) ){
406447
style_header("Error");
407448
@ Internal error: Fossil tried to make an invalid artifact for
408
- @ the edited technode.
449
+ @ the edited technote.
409450
style_footer();
410451
return;
411452
}
412
- assert( blob_is_reset(&event) );
413
- content_deltify(rid, nrid, 0);
414
- db_end_transaction(0);
415453
cgi_redirectf("technote?name=%T", zId);
416454
}
417455
if( P("cancel")!=0 ){
418456
cgi_redirectf("technote?name=%T", zId);
419457
return;
@@ -497,5 +535,61 @@
497535
@ <input type="submit" name="cancel" value="Cancel" />
498536
@ </td></tr></table>
499537
@ </div></form>
500538
style_footer();
501539
}
540
+
541
+/*
542
+** Add a new tech note to the repository. The timestamp is
543
+** given by the zETime parameter. isNew must be true to create
544
+** a new page. If no previous page with the name zPageName exists
545
+** and isNew is false, then this routine throws an error.
546
+*/
547
+void event_cmd_commit(
548
+ char *zETime, /* timestamp */
549
+ int isNew, /* true to create a new page */
550
+ Blob *pContent, /* content of the new page */
551
+ const char *zMimeType, /* mimetype of the content */
552
+ const char *zComment, /* comment to go on the timeline */
553
+ const char *zTags, /* tags */
554
+ const char *zClr /* background color */
555
+){
556
+ int rid; /* Artifact id of the tech note */
557
+ const char *zId; /* id of the tech note */
558
+ rid = db_int(0, "SELECT objid FROM event"
559
+ " WHERE datetime(mtime)=datetime('%q') AND type = 'e'"
560
+ " LIMIT 1",
561
+ zETime
562
+ );
563
+ if( rid==0 && !isNew ){
564
+#ifdef FOSSIL_ENABLE_JSON
565
+ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
566
+#endif
567
+ fossil_fatal("no such tech note: %s", zETime);
568
+ }
569
+ if( rid!=0 && isNew ){
570
+#ifdef FOSSIL_ENABLE_JSON
571
+ g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
572
+#endif
573
+ fossil_fatal("tech note %s already exists", zETime);
574
+ }
575
+
576
+ if ( isNew ){
577
+ zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
578
+ }else{
579
+ zId = db_text(0,
580
+ "SELECT substr(tagname,7) FROM tag"
581
+ " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
582
+ rid
583
+ );
584
+ }
585
+
586
+ user_select();
587
+ if (event_commit_common(rid, zId, blob_str(pContent), zETime,
588
+ zMimeType, zComment, zTags, zClr)==0 ){
589
+#ifdef FOSSIL_ENABLE_JSON
590
+ g.json.resultCode = FSL_JSON_E_ASSERT;
591
+#endif
592
+ fossil_fatal("Internal error: Fossil tried to make an "
593
+ "invalid artifact for the technote.");
594
+ }
595
+}
502596
--- src/event.c
+++ src/event.c
@@ -62,11 +62,11 @@
62 ** Display an existing event identified by EVENTID
63 */
64 void event_page(void){
65 int rid = 0; /* rid of the event artifact */
66 char *zUuid; /* UUID corresponding to rid */
67 const char *zId; /* Event identifier */
68 const char *zVerbose; /* Value of verbose option */
69 char *zETime; /* Time of the tech-note */
70 char *zATime; /* Time the artifact was created */
71 int specRid; /* rid specified by aid= parameter */
72 int prevRid, nextRid; /* Previous or next edits of this tech-note */
@@ -75,10 +75,11 @@
75 Blob title; /* Title extracted from the technote body */
76 Blob tail; /* Event body that comes after the title */
77 Stmt q1; /* Query to search for the technote */
78 int verboseFlag; /* True to show details */
79 const char *zMimetype = 0; /* Mimetype of the document */
 
80
81
82 /* wiki-read privilege is needed in order to read tech-notes.
83 */
84 login_check_credentials();
@@ -150,10 +151,15 @@
150 tail = fullbody;
151 }
152 style_header("%s", blob_str(&title));
153 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
154 style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
 
 
 
 
 
155 }
156 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
157 style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
158 if( g.perm.Hyperlink ){
159 if( verboseFlag ){
@@ -216,13 +222,123 @@
216 }else{
217 @ <pre>
218 @ %h(blob_str(&fullbody))
219 @ </pre>
220 }
 
 
 
 
 
221 style_footer();
222 manifest_destroy(pTNote);
223 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
225 /*
226 ** WEBPAGE: technoteedit
227 ** WEBPAGE: eventedit
228 **
@@ -323,97 +439,19 @@
323 );
324 }
325 }
326 zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
327 if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
328 char *zDate;
329 Blob cksum;
330 int nrid, n;
331 blob_init(&event, 0, 0);
332 db_begin_transaction();
333 login_verify_csrf_secret();
334 while( fossil_isspace(zComment[0]) ) zComment++;
335 n = strlen(zComment);
336 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
337 if( n>0 ){
338 blob_appendf(&event, "C %#F\n", n, zComment);
339 }
340 zDate = date_in_standard_format("now");
341 blob_appendf(&event, "D %s\n", zDate);
342 free(zDate);
343 zETime[10] = 'T';
344 blob_appendf(&event, "E %s %s\n", zETime, zId);
345 zETime[10] = ' ';
346 if( rid ){
347 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
348 blob_appendf(&event, "P %s\n", zUuid);
349 free(zUuid);
350 }
351 if( zMimetype && zMimetype[0] ){
352 blob_appendf(&event, "N %s\n", zMimetype);
353 }
354 if( zClr && zClr[0] ){
355 blob_appendf(&event, "T +bgcolor * %F\n", zClr);
356 }
357 if( zTags && zTags[0] ){
358 Blob tags, one;
359 int i, j;
360 Stmt q;
361 char *zBlob;
362
363 /* Load the tags string into a blob */
364 blob_zero(&tags);
365 blob_append(&tags, zTags, -1);
366
367 /* Collapse all sequences of whitespace and "," characters into
368 ** a single space character */
369 zBlob = blob_str(&tags);
370 for(i=j=0; zBlob[i]; i++, j++){
371 if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
372 while( fossil_isspace(zBlob[i+1]) ){ i++; }
373 zBlob[j] = ' ';
374 }else{
375 zBlob[j] = zBlob[i];
376 }
377 }
378 blob_resize(&tags, j);
379
380 /* Parse out each tag and load it into a temporary table for sorting */
381 db_multi_exec("CREATE TEMP TABLE newtags(x);");
382 while( blob_token(&tags, &one) ){
383 db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
384 }
385 blob_reset(&tags);
386
387 /* Extract the tags in sorted order and make an entry in the
388 ** artifact for each. */
389 db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
390 while( db_step(&q)==SQLITE_ROW ){
391 blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
392 }
393 db_finalize(&q);
394 }
395 if( !login_is_nobody() ){
396 blob_appendf(&event, "U %F\n", login_name());
397 }
398 blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
399 md5sum_blob(&event, &cksum);
400 blob_appendf(&event, "Z %b\n", &cksum);
401 blob_reset(&cksum);
402 nrid = content_put(&event);
403 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
404 if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
405 db_end_transaction(1);
406 style_header("Error");
407 @ Internal error: Fossil tried to make an invalid artifact for
408 @ the edited technode.
409 style_footer();
410 return;
411 }
412 assert( blob_is_reset(&event) );
413 content_deltify(rid, nrid, 0);
414 db_end_transaction(0);
415 cgi_redirectf("technote?name=%T", zId);
416 }
417 if( P("cancel")!=0 ){
418 cgi_redirectf("technote?name=%T", zId);
419 return;
@@ -497,5 +535,61 @@
497 @ <input type="submit" name="cancel" value="Cancel" />
498 @ </td></tr></table>
499 @ </div></form>
500 style_footer();
501 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
--- src/event.c
+++ src/event.c
@@ -62,11 +62,11 @@
62 ** Display an existing event identified by EVENTID
63 */
64 void event_page(void){
65 int rid = 0; /* rid of the event artifact */
66 char *zUuid; /* UUID corresponding to rid */
67 const char *zId; /* Event identifier */
68 const char *zVerbose; /* Value of verbose option */
69 char *zETime; /* Time of the tech-note */
70 char *zATime; /* Time the artifact was created */
71 int specRid; /* rid specified by aid= parameter */
72 int prevRid, nextRid; /* Previous or next edits of this tech-note */
@@ -75,10 +75,11 @@
75 Blob title; /* Title extracted from the technote body */
76 Blob tail; /* Event body that comes after the title */
77 Stmt q1; /* Query to search for the technote */
78 int verboseFlag; /* True to show details */
79 const char *zMimetype = 0; /* Mimetype of the document */
80 const char *zFullId; /* Full event identifier */
81
82
83 /* wiki-read privilege is needed in order to read tech-notes.
84 */
85 login_check_credentials();
@@ -150,10 +151,15 @@
151 tail = fullbody;
152 }
153 style_header("%s", blob_str(&title));
154 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
155 style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
156 if( g.perm.Attach ){
157 style_submenu_element("Attach", "Add an attachment",
158 "%R/attachadd?technote=%!S&from=%R/technote/%!S",
159 zId, zId);
160 }
161 }
162 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
163 style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
164 if( g.perm.Hyperlink ){
165 if( verboseFlag ){
@@ -216,13 +222,123 @@
222 }else{
223 @ <pre>
224 @ %h(blob_str(&fullbody))
225 @ </pre>
226 }
227 zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
228 " FROM tag"
229 " WHERE tagname GLOB 'event-%q*'",
230 zId);
231 attachment_list(zFullId, "<hr /><h2>Attachments:</h2><ul>");
232 style_footer();
233 manifest_destroy(pTNote);
234 }
235
236 /*
237 ** Add or update a new tech note to the repository. rid is id of
238 ** the prior version of this technote, if any.
239 **
240 ** returns 1 if the tech note was added or updated, 0 if the
241 ** update failed making an invalid artifact
242 */
243 int event_commit_common(
244 int rid, /* id of the prior version of the technote */
245 const char *zId, /* hash label for the technote */
246 const char *zBody, /* content of the technote */
247 char *zETime, /* timestamp for the technote */
248 const char *zMimetype, /* mimetype for the technote N-card */
249 const char *zComment, /* comment shown on the timeline */
250 const char *zTags, /* tags associated with this technote */
251 const char *zClr /* Background color */
252 ){
253 Blob event;
254 char *zDate;
255 Blob cksum;
256 int nrid, n;
257
258 blob_init(&event, 0, 0);
259 db_begin_transaction();
260 while( fossil_isspace(zComment[0]) ) zComment++;
261 n = strlen(zComment);
262 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
263 if( n>0 ){
264 blob_appendf(&event, "C %#F\n", n, zComment);
265 }
266 zDate = date_in_standard_format("now");
267 blob_appendf(&event, "D %s\n", zDate);
268 free(zDate);
269
270 zETime[10] = 'T';
271 blob_appendf(&event, "E %s %s\n", zETime, zId);
272 zETime[10] = ' ';
273 if( rid ){
274 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
275 blob_appendf(&event, "P %s\n", zUuid);
276 free(zUuid);
277 }
278 if( zMimetype && zMimetype[0] ){
279 blob_appendf(&event, "N %s\n", zMimetype);
280 }
281 if( zClr && zClr[0] ){
282 blob_appendf(&event, "T +bgcolor * %F\n", zClr);
283 }
284 if( zTags && zTags[0] ){
285 Blob tags, one;
286 int i, j;
287 Stmt q;
288 char *zBlob;
289
290 /* Load the tags string into a blob */
291 blob_zero(&tags);
292 blob_append(&tags, zTags, -1);
293
294 /* Collapse all sequences of whitespace and "," characters into
295 ** a single space character */
296 zBlob = blob_str(&tags);
297 for(i=j=0; zBlob[i]; i++, j++){
298 if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
299 while( fossil_isspace(zBlob[i+1]) ){ i++; }
300 zBlob[j] = ' ';
301 }else{
302 zBlob[j] = zBlob[i];
303 }
304 }
305 blob_resize(&tags, j);
306
307 /* Parse out each tag and load it into a temporary table for sorting */
308 db_multi_exec("CREATE TEMP TABLE newtags(x);");
309 while( blob_token(&tags, &one) ){
310 db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
311 }
312 blob_reset(&tags);
313
314 /* Extract the tags in sorted order and make an entry in the
315 ** artifact for each. */
316 db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
317 while( db_step(&q)==SQLITE_ROW ){
318 blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
319 }
320 db_finalize(&q);
321 }
322 if( !login_is_nobody() ){
323 blob_appendf(&event, "U %F\n", login_name());
324 }
325 blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
326 md5sum_blob(&event, &cksum);
327 blob_appendf(&event, "Z %b\n", &cksum);
328 blob_reset(&cksum);
329 nrid = content_put(&event);
330 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
331 if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
332 db_end_transaction(1);
333 return 0;
334 }
335 assert( blob_is_reset(&event) );
336 content_deltify(rid, nrid, 0);
337 db_end_transaction(0);
338 return 1;
339 }
340
341 /*
342 ** WEBPAGE: technoteedit
343 ** WEBPAGE: eventedit
344 **
@@ -323,97 +439,19 @@
439 );
440 }
441 }
442 zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
443 if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
 
 
 
 
 
444 login_verify_csrf_secret();
445 if ( !event_commit_common(rid, zId, zBody, zETime,
446 zMimetype, zComment, zTags, zClr) ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447 style_header("Error");
448 @ Internal error: Fossil tried to make an invalid artifact for
449 @ the edited technote.
450 style_footer();
451 return;
452 }
 
 
 
453 cgi_redirectf("technote?name=%T", zId);
454 }
455 if( P("cancel")!=0 ){
456 cgi_redirectf("technote?name=%T", zId);
457 return;
@@ -497,5 +535,61 @@
535 @ <input type="submit" name="cancel" value="Cancel" />
536 @ </td></tr></table>
537 @ </div></form>
538 style_footer();
539 }
540
541 /*
542 ** Add a new tech note to the repository. The timestamp is
543 ** given by the zETime parameter. isNew must be true to create
544 ** a new page. If no previous page with the name zPageName exists
545 ** and isNew is false, then this routine throws an error.
546 */
547 void event_cmd_commit(
548 char *zETime, /* timestamp */
549 int isNew, /* true to create a new page */
550 Blob *pContent, /* content of the new page */
551 const char *zMimeType, /* mimetype of the content */
552 const char *zComment, /* comment to go on the timeline */
553 const char *zTags, /* tags */
554 const char *zClr /* background color */
555 ){
556 int rid; /* Artifact id of the tech note */
557 const char *zId; /* id of the tech note */
558 rid = db_int(0, "SELECT objid FROM event"
559 " WHERE datetime(mtime)=datetime('%q') AND type = 'e'"
560 " LIMIT 1",
561 zETime
562 );
563 if( rid==0 && !isNew ){
564 #ifdef FOSSIL_ENABLE_JSON
565 g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
566 #endif
567 fossil_fatal("no such tech note: %s", zETime);
568 }
569 if( rid!=0 && isNew ){
570 #ifdef FOSSIL_ENABLE_JSON
571 g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
572 #endif
573 fossil_fatal("tech note %s already exists", zETime);
574 }
575
576 if ( isNew ){
577 zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
578 }else{
579 zId = db_text(0,
580 "SELECT substr(tagname,7) FROM tag"
581 " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
582 rid
583 );
584 }
585
586 user_select();
587 if (event_commit_common(rid, zId, blob_str(pContent), zETime,
588 zMimeType, zComment, zTags, zClr)==0 ){
589 #ifdef FOSSIL_ENABLE_JSON
590 g.json.resultCode = FSL_JSON_E_ASSERT;
591 #endif
592 fossil_fatal("Internal error: Fossil tried to make an "
593 "invalid artifact for the technote.");
594 }
595 }
596
+176 -82
--- src/event.c
+++ src/event.c
@@ -62,11 +62,11 @@
6262
** Display an existing event identified by EVENTID
6363
*/
6464
void event_page(void){
6565
int rid = 0; /* rid of the event artifact */
6666
char *zUuid; /* UUID corresponding to rid */
67
- const char *zId; /* Event identifier */
67
+ const char *zId; /* Event identifier */
6868
const char *zVerbose; /* Value of verbose option */
6969
char *zETime; /* Time of the tech-note */
7070
char *zATime; /* Time the artifact was created */
7171
int specRid; /* rid specified by aid= parameter */
7272
int prevRid, nextRid; /* Previous or next edits of this tech-note */
@@ -75,10 +75,11 @@
7575
Blob title; /* Title extracted from the technote body */
7676
Blob tail; /* Event body that comes after the title */
7777
Stmt q1; /* Query to search for the technote */
7878
int verboseFlag; /* True to show details */
7979
const char *zMimetype = 0; /* Mimetype of the document */
80
+ const char *zFullId; /* Full event identifier */
8081
8182
8283
/* wiki-read privilege is needed in order to read tech-notes.
8384
*/
8485
login_check_credentials();
@@ -150,10 +151,15 @@
150151
tail = fullbody;
151152
}
152153
style_header("%s", blob_str(&title));
153154
if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
154155
style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
156
+ if( g.perm.Attach ){
157
+ style_submenu_element("Attach", "Add an attachment",
158
+ "%R/attachadd?technote=%!S&from=%R/technote/%!S",
159
+ zId, zId);
160
+ }
155161
}
156162
zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
157163
style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
158164
if( g.perm.Hyperlink ){
159165
if( verboseFlag ){
@@ -216,13 +222,123 @@
216222
}else{
217223
@ <pre>
218224
@ %h(blob_str(&fullbody))
219225
@ </pre>
220226
}
227
+ zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
228
+ " FROM tag"
229
+ " WHERE tagname GLOB 'event-%q*'",
230
+ zId);
231
+ attachment_list(zFullId, "<hr /><h2>Attachments:</h2><ul>");
221232
style_footer();
222233
manifest_destroy(pTNote);
223234
}
235
+
236
+/*
237
+** Add or update a new tech note to the repository. rid is id of
238
+** the prior version of this technote, if any.
239
+**
240
+** returns 1 if the tech note was added or updated, 0 if the
241
+** update failed making an invalid artifact
242
+*/
243
+int event_commit_common(
244
+ int rid, /* id of the prior version of the technote */
245
+ const char *zId, /* hash label for the technote */
246
+ const char *zBody, /* content of the technote */
247
+ char *zETime, /* timestamp for the technote */
248
+ const char *zMimetype, /* mimetype for the technote N-card */
249
+ const char *zComment, /* comment shown on the timeline */
250
+ const char *zTags, /* tags associated with this technote */
251
+ const char *zClr /* Background color */
252
+){
253
+ Blob event;
254
+ char *zDate;
255
+ Blob cksum;
256
+ int nrid, n;
257
+
258
+ blob_init(&event, 0, 0);
259
+ db_begin_transaction();
260
+ while( fossil_isspace(zComment[0]) ) zComment++;
261
+ n = strlen(zComment);
262
+ while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
263
+ if( n>0 ){
264
+ blob_appendf(&event, "C %#F\n", n, zComment);
265
+ }
266
+ zDate = date_in_standard_format("now");
267
+ blob_appendf(&event, "D %s\n", zDate);
268
+ free(zDate);
269
+
270
+ zETime[10] = 'T';
271
+ blob_appendf(&event, "E %s %s\n", zETime, zId);
272
+ zETime[10] = ' ';
273
+ if( rid ){
274
+ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
275
+ blob_appendf(&event, "P %s\n", zUuid);
276
+ free(zUuid);
277
+ }
278
+ if( zMimetype && zMimetype[0] ){
279
+ blob_appendf(&event, "N %s\n", zMimetype);
280
+ }
281
+ if( zClr && zClr[0] ){
282
+ blob_appendf(&event, "T +bgcolor * %F\n", zClr);
283
+ }
284
+ if( zTags && zTags[0] ){
285
+ Blob tags, one;
286
+ int i, j;
287
+ Stmt q;
288
+ char *zBlob;
289
+
290
+ /* Load the tags string into a blob */
291
+ blob_zero(&tags);
292
+ blob_append(&tags, zTags, -1);
293
+
294
+ /* Collapse all sequences of whitespace and "," characters into
295
+ ** a single space character */
296
+ zBlob = blob_str(&tags);
297
+ for(i=j=0; zBlob[i]; i++, j++){
298
+ if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
299
+ while( fossil_isspace(zBlob[i+1]) ){ i++; }
300
+ zBlob[j] = ' ';
301
+ }else{
302
+ zBlob[j] = zBlob[i];
303
+ }
304
+ }
305
+ blob_resize(&tags, j);
306
+
307
+ /* Parse out each tag and load it into a temporary table for sorting */
308
+ db_multi_exec("CREATE TEMP TABLE newtags(x);");
309
+ while( blob_token(&tags, &one) ){
310
+ db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
311
+ }
312
+ blob_reset(&tags);
313
+
314
+ /* Extract the tags in sorted order and make an entry in the
315
+ ** artifact for each. */
316
+ db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
317
+ while( db_step(&q)==SQLITE_ROW ){
318
+ blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
319
+ }
320
+ db_finalize(&q);
321
+ }
322
+ if( !login_is_nobody() ){
323
+ blob_appendf(&event, "U %F\n", login_name());
324
+ }
325
+ blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
326
+ md5sum_blob(&event, &cksum);
327
+ blob_appendf(&event, "Z %b\n", &cksum);
328
+ blob_reset(&cksum);
329
+ nrid = content_put(&event);
330
+ db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
331
+ if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
332
+ db_end_transaction(1);
333
+ return 0;
334
+ }
335
+ assert( blob_is_reset(&event) );
336
+ content_deltify(rid, nrid, 0);
337
+ db_end_transaction(0);
338
+ return 1;
339
+}
224340
225341
/*
226342
** WEBPAGE: technoteedit
227343
** WEBPAGE: eventedit
228344
**
@@ -323,97 +439,19 @@
323439
);
324440
}
325441
}
326442
zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
327443
if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
328
- char *zDate;
329
- Blob cksum;
330
- int nrid, n;
331
- blob_init(&event, 0, 0);
332
- db_begin_transaction();
333444
login_verify_csrf_secret();
334
- while( fossil_isspace(zComment[0]) ) zComment++;
335
- n = strlen(zComment);
336
- while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
337
- if( n>0 ){
338
- blob_appendf(&event, "C %#F\n", n, zComment);
339
- }
340
- zDate = date_in_standard_format("now");
341
- blob_appendf(&event, "D %s\n", zDate);
342
- free(zDate);
343
- zETime[10] = 'T';
344
- blob_appendf(&event, "E %s %s\n", zETime, zId);
345
- zETime[10] = ' ';
346
- if( rid ){
347
- char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
348
- blob_appendf(&event, "P %s\n", zUuid);
349
- free(zUuid);
350
- }
351
- if( zMimetype && zMimetype[0] ){
352
- blob_appendf(&event, "N %s\n", zMimetype);
353
- }
354
- if( zClr && zClr[0] ){
355
- blob_appendf(&event, "T +bgcolor * %F\n", zClr);
356
- }
357
- if( zTags && zTags[0] ){
358
- Blob tags, one;
359
- int i, j;
360
- Stmt q;
361
- char *zBlob;
362
-
363
- /* Load the tags string into a blob */
364
- blob_zero(&tags);
365
- blob_append(&tags, zTags, -1);
366
-
367
- /* Collapse all sequences of whitespace and "," characters into
368
- ** a single space character */
369
- zBlob = blob_str(&tags);
370
- for(i=j=0; zBlob[i]; i++, j++){
371
- if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
372
- while( fossil_isspace(zBlob[i+1]) ){ i++; }
373
- zBlob[j] = ' ';
374
- }else{
375
- zBlob[j] = zBlob[i];
376
- }
377
- }
378
- blob_resize(&tags, j);
379
-
380
- /* Parse out each tag and load it into a temporary table for sorting */
381
- db_multi_exec("CREATE TEMP TABLE newtags(x);");
382
- while( blob_token(&tags, &one) ){
383
- db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
384
- }
385
- blob_reset(&tags);
386
-
387
- /* Extract the tags in sorted order and make an entry in the
388
- ** artifact for each. */
389
- db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
390
- while( db_step(&q)==SQLITE_ROW ){
391
- blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
392
- }
393
- db_finalize(&q);
394
- }
395
- if( !login_is_nobody() ){
396
- blob_appendf(&event, "U %F\n", login_name());
397
- }
398
- blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
399
- md5sum_blob(&event, &cksum);
400
- blob_appendf(&event, "Z %b\n", &cksum);
401
- blob_reset(&cksum);
402
- nrid = content_put(&event);
403
- db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
404
- if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
405
- db_end_transaction(1);
445
+ if ( !event_commit_common(rid, zId, zBody, zETime,
446
+ zMimetype, zComment, zTags, zClr) ){
406447
style_header("Error");
407448
@ Internal error: Fossil tried to make an invalid artifact for
408
- @ the edited technode.
449
+ @ the edited technote.
409450
style_footer();
410451
return;
411452
}
412
- assert( blob_is_reset(&event) );
413
- content_deltify(rid, nrid, 0);
414
- db_end_transaction(0);
415453
cgi_redirectf("technote?name=%T", zId);
416454
}
417455
if( P("cancel")!=0 ){
418456
cgi_redirectf("technote?name=%T", zId);
419457
return;
@@ -497,5 +535,61 @@
497535
@ <input type="submit" name="cancel" value="Cancel" />
498536
@ </td></tr></table>
499537
@ </div></form>
500538
style_footer();
501539
}
540
+
541
+/*
542
+** Add a new tech note to the repository. The timestamp is
543
+** given by the zETime parameter. isNew must be true to create
544
+** a new page. If no previous page with the name zPageName exists
545
+** and isNew is false, then this routine throws an error.
546
+*/
547
+void event_cmd_commit(
548
+ char *zETime, /* timestamp */
549
+ int isNew, /* true to create a new page */
550
+ Blob *pContent, /* content of the new page */
551
+ const char *zMimeType, /* mimetype of the content */
552
+ const char *zComment, /* comment to go on the timeline */
553
+ const char *zTags, /* tags */
554
+ const char *zClr /* background color */
555
+){
556
+ int rid; /* Artifact id of the tech note */
557
+ const char *zId; /* id of the tech note */
558
+ rid = db_int(0, "SELECT objid FROM event"
559
+ " WHERE datetime(mtime)=datetime('%q') AND type = 'e'"
560
+ " LIMIT 1",
561
+ zETime
562
+ );
563
+ if( rid==0 && !isNew ){
564
+#ifdef FOSSIL_ENABLE_JSON
565
+ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
566
+#endif
567
+ fossil_fatal("no such tech note: %s", zETime);
568
+ }
569
+ if( rid!=0 && isNew ){
570
+#ifdef FOSSIL_ENABLE_JSON
571
+ g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
572
+#endif
573
+ fossil_fatal("tech note %s already exists", zETime);
574
+ }
575
+
576
+ if ( isNew ){
577
+ zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
578
+ }else{
579
+ zId = db_text(0,
580
+ "SELECT substr(tagname,7) FROM tag"
581
+ " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
582
+ rid
583
+ );
584
+ }
585
+
586
+ user_select();
587
+ if (event_commit_common(rid, zId, blob_str(pContent), zETime,
588
+ zMimeType, zComment, zTags, zClr)==0 ){
589
+#ifdef FOSSIL_ENABLE_JSON
590
+ g.json.resultCode = FSL_JSON_E_ASSERT;
591
+#endif
592
+ fossil_fatal("Internal error: Fossil tried to make an "
593
+ "invalid artifact for the technote.");
594
+ }
595
+}
502596
--- src/event.c
+++ src/event.c
@@ -62,11 +62,11 @@
62 ** Display an existing event identified by EVENTID
63 */
64 void event_page(void){
65 int rid = 0; /* rid of the event artifact */
66 char *zUuid; /* UUID corresponding to rid */
67 const char *zId; /* Event identifier */
68 const char *zVerbose; /* Value of verbose option */
69 char *zETime; /* Time of the tech-note */
70 char *zATime; /* Time the artifact was created */
71 int specRid; /* rid specified by aid= parameter */
72 int prevRid, nextRid; /* Previous or next edits of this tech-note */
@@ -75,10 +75,11 @@
75 Blob title; /* Title extracted from the technote body */
76 Blob tail; /* Event body that comes after the title */
77 Stmt q1; /* Query to search for the technote */
78 int verboseFlag; /* True to show details */
79 const char *zMimetype = 0; /* Mimetype of the document */
 
80
81
82 /* wiki-read privilege is needed in order to read tech-notes.
83 */
84 login_check_credentials();
@@ -150,10 +151,15 @@
150 tail = fullbody;
151 }
152 style_header("%s", blob_str(&title));
153 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
154 style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
 
 
 
 
 
155 }
156 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
157 style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
158 if( g.perm.Hyperlink ){
159 if( verboseFlag ){
@@ -216,13 +222,123 @@
216 }else{
217 @ <pre>
218 @ %h(blob_str(&fullbody))
219 @ </pre>
220 }
 
 
 
 
 
221 style_footer();
222 manifest_destroy(pTNote);
223 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
225 /*
226 ** WEBPAGE: technoteedit
227 ** WEBPAGE: eventedit
228 **
@@ -323,97 +439,19 @@
323 );
324 }
325 }
326 zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
327 if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
328 char *zDate;
329 Blob cksum;
330 int nrid, n;
331 blob_init(&event, 0, 0);
332 db_begin_transaction();
333 login_verify_csrf_secret();
334 while( fossil_isspace(zComment[0]) ) zComment++;
335 n = strlen(zComment);
336 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
337 if( n>0 ){
338 blob_appendf(&event, "C %#F\n", n, zComment);
339 }
340 zDate = date_in_standard_format("now");
341 blob_appendf(&event, "D %s\n", zDate);
342 free(zDate);
343 zETime[10] = 'T';
344 blob_appendf(&event, "E %s %s\n", zETime, zId);
345 zETime[10] = ' ';
346 if( rid ){
347 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
348 blob_appendf(&event, "P %s\n", zUuid);
349 free(zUuid);
350 }
351 if( zMimetype && zMimetype[0] ){
352 blob_appendf(&event, "N %s\n", zMimetype);
353 }
354 if( zClr && zClr[0] ){
355 blob_appendf(&event, "T +bgcolor * %F\n", zClr);
356 }
357 if( zTags && zTags[0] ){
358 Blob tags, one;
359 int i, j;
360 Stmt q;
361 char *zBlob;
362
363 /* Load the tags string into a blob */
364 blob_zero(&tags);
365 blob_append(&tags, zTags, -1);
366
367 /* Collapse all sequences of whitespace and "," characters into
368 ** a single space character */
369 zBlob = blob_str(&tags);
370 for(i=j=0; zBlob[i]; i++, j++){
371 if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
372 while( fossil_isspace(zBlob[i+1]) ){ i++; }
373 zBlob[j] = ' ';
374 }else{
375 zBlob[j] = zBlob[i];
376 }
377 }
378 blob_resize(&tags, j);
379
380 /* Parse out each tag and load it into a temporary table for sorting */
381 db_multi_exec("CREATE TEMP TABLE newtags(x);");
382 while( blob_token(&tags, &one) ){
383 db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
384 }
385 blob_reset(&tags);
386
387 /* Extract the tags in sorted order and make an entry in the
388 ** artifact for each. */
389 db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
390 while( db_step(&q)==SQLITE_ROW ){
391 blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
392 }
393 db_finalize(&q);
394 }
395 if( !login_is_nobody() ){
396 blob_appendf(&event, "U %F\n", login_name());
397 }
398 blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
399 md5sum_blob(&event, &cksum);
400 blob_appendf(&event, "Z %b\n", &cksum);
401 blob_reset(&cksum);
402 nrid = content_put(&event);
403 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
404 if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
405 db_end_transaction(1);
406 style_header("Error");
407 @ Internal error: Fossil tried to make an invalid artifact for
408 @ the edited technode.
409 style_footer();
410 return;
411 }
412 assert( blob_is_reset(&event) );
413 content_deltify(rid, nrid, 0);
414 db_end_transaction(0);
415 cgi_redirectf("technote?name=%T", zId);
416 }
417 if( P("cancel")!=0 ){
418 cgi_redirectf("technote?name=%T", zId);
419 return;
@@ -497,5 +535,61 @@
497 @ <input type="submit" name="cancel" value="Cancel" />
498 @ </td></tr></table>
499 @ </div></form>
500 style_footer();
501 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
--- src/event.c
+++ src/event.c
@@ -62,11 +62,11 @@
62 ** Display an existing event identified by EVENTID
63 */
64 void event_page(void){
65 int rid = 0; /* rid of the event artifact */
66 char *zUuid; /* UUID corresponding to rid */
67 const char *zId; /* Event identifier */
68 const char *zVerbose; /* Value of verbose option */
69 char *zETime; /* Time of the tech-note */
70 char *zATime; /* Time the artifact was created */
71 int specRid; /* rid specified by aid= parameter */
72 int prevRid, nextRid; /* Previous or next edits of this tech-note */
@@ -75,10 +75,11 @@
75 Blob title; /* Title extracted from the technote body */
76 Blob tail; /* Event body that comes after the title */
77 Stmt q1; /* Query to search for the technote */
78 int verboseFlag; /* True to show details */
79 const char *zMimetype = 0; /* Mimetype of the document */
80 const char *zFullId; /* Full event identifier */
81
82
83 /* wiki-read privilege is needed in order to read tech-notes.
84 */
85 login_check_credentials();
@@ -150,10 +151,15 @@
151 tail = fullbody;
152 }
153 style_header("%s", blob_str(&title));
154 if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
155 style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
156 if( g.perm.Attach ){
157 style_submenu_element("Attach", "Add an attachment",
158 "%R/attachadd?technote=%!S&from=%R/technote/%!S",
159 zId, zId);
160 }
161 }
162 zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
163 style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
164 if( g.perm.Hyperlink ){
165 if( verboseFlag ){
@@ -216,13 +222,123 @@
222 }else{
223 @ <pre>
224 @ %h(blob_str(&fullbody))
225 @ </pre>
226 }
227 zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
228 " FROM tag"
229 " WHERE tagname GLOB 'event-%q*'",
230 zId);
231 attachment_list(zFullId, "<hr /><h2>Attachments:</h2><ul>");
232 style_footer();
233 manifest_destroy(pTNote);
234 }
235
236 /*
237 ** Add or update a new tech note to the repository. rid is id of
238 ** the prior version of this technote, if any.
239 **
240 ** returns 1 if the tech note was added or updated, 0 if the
241 ** update failed making an invalid artifact
242 */
243 int event_commit_common(
244 int rid, /* id of the prior version of the technote */
245 const char *zId, /* hash label for the technote */
246 const char *zBody, /* content of the technote */
247 char *zETime, /* timestamp for the technote */
248 const char *zMimetype, /* mimetype for the technote N-card */
249 const char *zComment, /* comment shown on the timeline */
250 const char *zTags, /* tags associated with this technote */
251 const char *zClr /* Background color */
252 ){
253 Blob event;
254 char *zDate;
255 Blob cksum;
256 int nrid, n;
257
258 blob_init(&event, 0, 0);
259 db_begin_transaction();
260 while( fossil_isspace(zComment[0]) ) zComment++;
261 n = strlen(zComment);
262 while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
263 if( n>0 ){
264 blob_appendf(&event, "C %#F\n", n, zComment);
265 }
266 zDate = date_in_standard_format("now");
267 blob_appendf(&event, "D %s\n", zDate);
268 free(zDate);
269
270 zETime[10] = 'T';
271 blob_appendf(&event, "E %s %s\n", zETime, zId);
272 zETime[10] = ' ';
273 if( rid ){
274 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
275 blob_appendf(&event, "P %s\n", zUuid);
276 free(zUuid);
277 }
278 if( zMimetype && zMimetype[0] ){
279 blob_appendf(&event, "N %s\n", zMimetype);
280 }
281 if( zClr && zClr[0] ){
282 blob_appendf(&event, "T +bgcolor * %F\n", zClr);
283 }
284 if( zTags && zTags[0] ){
285 Blob tags, one;
286 int i, j;
287 Stmt q;
288 char *zBlob;
289
290 /* Load the tags string into a blob */
291 blob_zero(&tags);
292 blob_append(&tags, zTags, -1);
293
294 /* Collapse all sequences of whitespace and "," characters into
295 ** a single space character */
296 zBlob = blob_str(&tags);
297 for(i=j=0; zBlob[i]; i++, j++){
298 if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
299 while( fossil_isspace(zBlob[i+1]) ){ i++; }
300 zBlob[j] = ' ';
301 }else{
302 zBlob[j] = zBlob[i];
303 }
304 }
305 blob_resize(&tags, j);
306
307 /* Parse out each tag and load it into a temporary table for sorting */
308 db_multi_exec("CREATE TEMP TABLE newtags(x);");
309 while( blob_token(&tags, &one) ){
310 db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
311 }
312 blob_reset(&tags);
313
314 /* Extract the tags in sorted order and make an entry in the
315 ** artifact for each. */
316 db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
317 while( db_step(&q)==SQLITE_ROW ){
318 blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
319 }
320 db_finalize(&q);
321 }
322 if( !login_is_nobody() ){
323 blob_appendf(&event, "U %F\n", login_name());
324 }
325 blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
326 md5sum_blob(&event, &cksum);
327 blob_appendf(&event, "Z %b\n", &cksum);
328 blob_reset(&cksum);
329 nrid = content_put(&event);
330 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
331 if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
332 db_end_transaction(1);
333 return 0;
334 }
335 assert( blob_is_reset(&event) );
336 content_deltify(rid, nrid, 0);
337 db_end_transaction(0);
338 return 1;
339 }
340
341 /*
342 ** WEBPAGE: technoteedit
343 ** WEBPAGE: eventedit
344 **
@@ -323,97 +439,19 @@
439 );
440 }
441 }
442 zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
443 if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
 
 
 
 
 
444 login_verify_csrf_secret();
445 if ( !event_commit_common(rid, zId, zBody, zETime,
446 zMimetype, zComment, zTags, zClr) ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447 style_header("Error");
448 @ Internal error: Fossil tried to make an invalid artifact for
449 @ the edited technote.
450 style_footer();
451 return;
452 }
 
 
 
453 cgi_redirectf("technote?name=%T", zId);
454 }
455 if( P("cancel")!=0 ){
456 cgi_redirectf("technote?name=%T", zId);
457 return;
@@ -497,5 +535,61 @@
535 @ <input type="submit" name="cancel" value="Cancel" />
536 @ </td></tr></table>
537 @ </div></form>
538 style_footer();
539 }
540
541 /*
542 ** Add a new tech note to the repository. The timestamp is
543 ** given by the zETime parameter. isNew must be true to create
544 ** a new page. If no previous page with the name zPageName exists
545 ** and isNew is false, then this routine throws an error.
546 */
547 void event_cmd_commit(
548 char *zETime, /* timestamp */
549 int isNew, /* true to create a new page */
550 Blob *pContent, /* content of the new page */
551 const char *zMimeType, /* mimetype of the content */
552 const char *zComment, /* comment to go on the timeline */
553 const char *zTags, /* tags */
554 const char *zClr /* background color */
555 ){
556 int rid; /* Artifact id of the tech note */
557 const char *zId; /* id of the tech note */
558 rid = db_int(0, "SELECT objid FROM event"
559 " WHERE datetime(mtime)=datetime('%q') AND type = 'e'"
560 " LIMIT 1",
561 zETime
562 );
563 if( rid==0 && !isNew ){
564 #ifdef FOSSIL_ENABLE_JSON
565 g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
566 #endif
567 fossil_fatal("no such tech note: %s", zETime);
568 }
569 if( rid!=0 && isNew ){
570 #ifdef FOSSIL_ENABLE_JSON
571 g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
572 #endif
573 fossil_fatal("tech note %s already exists", zETime);
574 }
575
576 if ( isNew ){
577 zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
578 }else{
579 zId = db_text(0,
580 "SELECT substr(tagname,7) FROM tag"
581 " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
582 rid
583 );
584 }
585
586 user_select();
587 if (event_commit_common(rid, zId, blob_str(pContent), zETime,
588 zMimeType, zComment, zTags, zClr)==0 ){
589 #ifdef FOSSIL_ENABLE_JSON
590 g.json.resultCode = FSL_JSON_E_ASSERT;
591 #endif
592 fossil_fatal("Internal error: Fossil tried to make an "
593 "invalid artifact for the technote.");
594 }
595 }
596
+82 -3
--- src/manifest.c
+++ src/manifest.c
@@ -2008,10 +2008,11 @@
20082008
char *zTag = mprintf("event-%s", p->zEventId);
20092009
int tagid = tag_findid(zTag, 1);
20102010
int prior, subsequent;
20112011
int nWiki;
20122012
char zLength[40];
2013
+ Stmt qatt;
20132014
while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
20142015
nWiki = strlen(p->zWiki);
20152016
sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
20162017
tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
20172018
fossil_free(zTag);
@@ -2049,26 +2050,95 @@
20492050
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
20502051
p->rEventDate, rid, tagid, p->zUser, p->zComment,
20512052
TAG_BGCOLOR, rid
20522053
);
20532054
}
2055
+ /* Locate and update comment for any attachments */
2056
+ db_prepare(&qatt,
2057
+ "SELECT attachid, src, target, filename FROM attachment"
2058
+ " WHERE target=%Q",
2059
+ p->zEventId
2060
+ );
2061
+ while( db_step(&qatt)==SQLITE_ROW ){
2062
+ const char *zAttachId = db_column_text(&qatt, 0);
2063
+ const char *zSrc = db_column_text(&qatt, 1);
2064
+ const char *zTarget = db_column_text(&qatt, 2);
2065
+ const char *zName = db_column_text(&qatt, 3);
2066
+ const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2067
+ char *zComment;
2068
+ if( isAdd ){
2069
+ zComment = mprintf(
2070
+ "Add attachment [/artifact/%!S|%h] to"
2071
+ " tech note [/technote/%h|%.10h]",
2072
+ zSrc, zName, zTarget, zTarget);
2073
+ }else{
2074
+ zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2075
+ zName, zTarget);
2076
+ }
2077
+ db_multi_exec("UPDATE event SET comment=%Q, type='e'"
2078
+ " WHERE objid=%Q",
2079
+ zComment, zAttachId);
2080
+ fossil_free(zComment);
2081
+ }
2082
+ db_finalize(&qatt);
20542083
}
20552084
if( p->type==CFTYPE_TICKET ){
20562085
char *zTag;
2086
+ Stmt qatt;
20572087
assert( manifest_crosslink_busy==1 );
20582088
zTag = mprintf("tkt-%s", p->zTicketUuid);
20592089
tag_insert(zTag, 1, 0, rid, p->rDate, rid);
20602090
fossil_free(zTag);
20612091
db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
20622092
p->zTicketUuid);
2093
+ /* Locate and update comment for any attachments */
2094
+ db_prepare(&qatt,
2095
+ "SELECT attachid, src, target, filename FROM attachment"
2096
+ " WHERE target=%Q",
2097
+ p->zTicketUuid
2098
+ );
2099
+ while( db_step(&qatt)==SQLITE_ROW ){
2100
+ const char *zAttachId = db_column_text(&qatt, 0);
2101
+ const char *zSrc = db_column_text(&qatt, 1);
2102
+ const char *zTarget = db_column_text(&qatt, 2);
2103
+ const char *zName = db_column_text(&qatt, 3);
2104
+ const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2105
+ char *zComment;
2106
+ if( isAdd ){
2107
+ zComment = mprintf(
2108
+ "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2109
+ zSrc, zName, zTarget, zTarget);
2110
+ }else{
2111
+ zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]",
2112
+ zName, zTarget, zTarget);
2113
+ }
2114
+ db_multi_exec("UPDATE event SET comment=%Q, type='t'"
2115
+ " WHERE objid=%Q",
2116
+ zComment, zAttachId);
2117
+ fossil_free(zComment);
2118
+ }
2119
+ db_finalize(&qatt);
20632120
}
20642121
if( p->type==CFTYPE_ATTACHMENT ){
20652122
char *zComment = 0;
20662123
const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
2067
- const char attachToType = fossil_is_uuid(p->zAttachTarget)
2068
- ? 't' /* attach to ticket */
2069
- : 'w' /* attach to wiki page */;
2124
+ /* We assume that we're attaching to a wiki page until we
2125
+ ** prove otherwise (which could on a later artifact if we
2126
+ ** process the attachment artifact before the artifact to
2127
+ ** which it is attached!) */
2128
+ char attachToType = 'w';
2129
+ if( fossil_is_uuid(p->zAttachTarget) ){
2130
+ if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
2131
+ p->zAttachTarget)
2132
+ ){
2133
+ attachToType = 't'; /* Attaching to known ticket */
2134
+ }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",
2135
+ p->zAttachTarget)
2136
+ ){
2137
+ attachToType = 'e'; /* Attaching to known tech note */
2138
+ }
2139
+ }
20702140
db_multi_exec(
20712141
"INSERT INTO attachment(attachid, mtime, src, target,"
20722142
"filename, comment, user)"
20732143
"VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
20742144
rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
@@ -2089,10 +2159,19 @@
20892159
p->zAttachSrc, p->zAttachName, p->zAttachTarget);
20902160
}else{
20912161
zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
20922162
p->zAttachName, p->zAttachTarget);
20932163
}
2164
+ }else if( 'e' == attachToType ){
2165
+ if( isAdd ){
2166
+ zComment = mprintf(
2167
+ "Add attachment [/artifact/%!S|%h] to tech note [/technote/%h|%.10h]",
2168
+ p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2169
+ }else{
2170
+ zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2171
+ p->zAttachName, p->zAttachTarget);
2172
+ }
20942173
}else{
20952174
if( isAdd ){
20962175
zComment = mprintf(
20972176
"Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
20982177
p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
20992178
--- src/manifest.c
+++ src/manifest.c
@@ -2008,10 +2008,11 @@
2008 char *zTag = mprintf("event-%s", p->zEventId);
2009 int tagid = tag_findid(zTag, 1);
2010 int prior, subsequent;
2011 int nWiki;
2012 char zLength[40];
 
2013 while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
2014 nWiki = strlen(p->zWiki);
2015 sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
2016 tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
2017 fossil_free(zTag);
@@ -2049,26 +2050,95 @@
2049 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
2050 p->rEventDate, rid, tagid, p->zUser, p->zComment,
2051 TAG_BGCOLOR, rid
2052 );
2053 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2054 }
2055 if( p->type==CFTYPE_TICKET ){
2056 char *zTag;
 
2057 assert( manifest_crosslink_busy==1 );
2058 zTag = mprintf("tkt-%s", p->zTicketUuid);
2059 tag_insert(zTag, 1, 0, rid, p->rDate, rid);
2060 fossil_free(zTag);
2061 db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
2062 p->zTicketUuid);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2063 }
2064 if( p->type==CFTYPE_ATTACHMENT ){
2065 char *zComment = 0;
2066 const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
2067 const char attachToType = fossil_is_uuid(p->zAttachTarget)
2068 ? 't' /* attach to ticket */
2069 : 'w' /* attach to wiki page */;
 
 
 
 
 
 
 
 
 
 
 
 
 
2070 db_multi_exec(
2071 "INSERT INTO attachment(attachid, mtime, src, target,"
2072 "filename, comment, user)"
2073 "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
2074 rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
@@ -2089,10 +2159,19 @@
2089 p->zAttachSrc, p->zAttachName, p->zAttachTarget);
2090 }else{
2091 zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
2092 p->zAttachName, p->zAttachTarget);
2093 }
 
 
 
 
 
 
 
 
 
2094 }else{
2095 if( isAdd ){
2096 zComment = mprintf(
2097 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2098 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2099
--- src/manifest.c
+++ src/manifest.c
@@ -2008,10 +2008,11 @@
2008 char *zTag = mprintf("event-%s", p->zEventId);
2009 int tagid = tag_findid(zTag, 1);
2010 int prior, subsequent;
2011 int nWiki;
2012 char zLength[40];
2013 Stmt qatt;
2014 while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
2015 nWiki = strlen(p->zWiki);
2016 sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
2017 tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
2018 fossil_free(zTag);
@@ -2049,26 +2050,95 @@
2050 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
2051 p->rEventDate, rid, tagid, p->zUser, p->zComment,
2052 TAG_BGCOLOR, rid
2053 );
2054 }
2055 /* Locate and update comment for any attachments */
2056 db_prepare(&qatt,
2057 "SELECT attachid, src, target, filename FROM attachment"
2058 " WHERE target=%Q",
2059 p->zEventId
2060 );
2061 while( db_step(&qatt)==SQLITE_ROW ){
2062 const char *zAttachId = db_column_text(&qatt, 0);
2063 const char *zSrc = db_column_text(&qatt, 1);
2064 const char *zTarget = db_column_text(&qatt, 2);
2065 const char *zName = db_column_text(&qatt, 3);
2066 const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2067 char *zComment;
2068 if( isAdd ){
2069 zComment = mprintf(
2070 "Add attachment [/artifact/%!S|%h] to"
2071 " tech note [/technote/%h|%.10h]",
2072 zSrc, zName, zTarget, zTarget);
2073 }else{
2074 zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2075 zName, zTarget);
2076 }
2077 db_multi_exec("UPDATE event SET comment=%Q, type='e'"
2078 " WHERE objid=%Q",
2079 zComment, zAttachId);
2080 fossil_free(zComment);
2081 }
2082 db_finalize(&qatt);
2083 }
2084 if( p->type==CFTYPE_TICKET ){
2085 char *zTag;
2086 Stmt qatt;
2087 assert( manifest_crosslink_busy==1 );
2088 zTag = mprintf("tkt-%s", p->zTicketUuid);
2089 tag_insert(zTag, 1, 0, rid, p->rDate, rid);
2090 fossil_free(zTag);
2091 db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
2092 p->zTicketUuid);
2093 /* Locate and update comment for any attachments */
2094 db_prepare(&qatt,
2095 "SELECT attachid, src, target, filename FROM attachment"
2096 " WHERE target=%Q",
2097 p->zTicketUuid
2098 );
2099 while( db_step(&qatt)==SQLITE_ROW ){
2100 const char *zAttachId = db_column_text(&qatt, 0);
2101 const char *zSrc = db_column_text(&qatt, 1);
2102 const char *zTarget = db_column_text(&qatt, 2);
2103 const char *zName = db_column_text(&qatt, 3);
2104 const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2105 char *zComment;
2106 if( isAdd ){
2107 zComment = mprintf(
2108 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2109 zSrc, zName, zTarget, zTarget);
2110 }else{
2111 zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]",
2112 zName, zTarget, zTarget);
2113 }
2114 db_multi_exec("UPDATE event SET comment=%Q, type='t'"
2115 " WHERE objid=%Q",
2116 zComment, zAttachId);
2117 fossil_free(zComment);
2118 }
2119 db_finalize(&qatt);
2120 }
2121 if( p->type==CFTYPE_ATTACHMENT ){
2122 char *zComment = 0;
2123 const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
2124 /* We assume that we're attaching to a wiki page until we
2125 ** prove otherwise (which could on a later artifact if we
2126 ** process the attachment artifact before the artifact to
2127 ** which it is attached!) */
2128 char attachToType = 'w';
2129 if( fossil_is_uuid(p->zAttachTarget) ){
2130 if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
2131 p->zAttachTarget)
2132 ){
2133 attachToType = 't'; /* Attaching to known ticket */
2134 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",
2135 p->zAttachTarget)
2136 ){
2137 attachToType = 'e'; /* Attaching to known tech note */
2138 }
2139 }
2140 db_multi_exec(
2141 "INSERT INTO attachment(attachid, mtime, src, target,"
2142 "filename, comment, user)"
2143 "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
2144 rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
@@ -2089,10 +2159,19 @@
2159 p->zAttachSrc, p->zAttachName, p->zAttachTarget);
2160 }else{
2161 zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
2162 p->zAttachName, p->zAttachTarget);
2163 }
2164 }else if( 'e' == attachToType ){
2165 if( isAdd ){
2166 zComment = mprintf(
2167 "Add attachment [/artifact/%!S|%h] to tech note [/technote/%h|%.10h]",
2168 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2169 }else{
2170 zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2171 p->zAttachName, p->zAttachTarget);
2172 }
2173 }else{
2174 if( isAdd ){
2175 zComment = mprintf(
2176 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2177 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2178
+82 -3
--- src/manifest.c
+++ src/manifest.c
@@ -2008,10 +2008,11 @@
20082008
char *zTag = mprintf("event-%s", p->zEventId);
20092009
int tagid = tag_findid(zTag, 1);
20102010
int prior, subsequent;
20112011
int nWiki;
20122012
char zLength[40];
2013
+ Stmt qatt;
20132014
while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
20142015
nWiki = strlen(p->zWiki);
20152016
sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
20162017
tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
20172018
fossil_free(zTag);
@@ -2049,26 +2050,95 @@
20492050
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
20502051
p->rEventDate, rid, tagid, p->zUser, p->zComment,
20512052
TAG_BGCOLOR, rid
20522053
);
20532054
}
2055
+ /* Locate and update comment for any attachments */
2056
+ db_prepare(&qatt,
2057
+ "SELECT attachid, src, target, filename FROM attachment"
2058
+ " WHERE target=%Q",
2059
+ p->zEventId
2060
+ );
2061
+ while( db_step(&qatt)==SQLITE_ROW ){
2062
+ const char *zAttachId = db_column_text(&qatt, 0);
2063
+ const char *zSrc = db_column_text(&qatt, 1);
2064
+ const char *zTarget = db_column_text(&qatt, 2);
2065
+ const char *zName = db_column_text(&qatt, 3);
2066
+ const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2067
+ char *zComment;
2068
+ if( isAdd ){
2069
+ zComment = mprintf(
2070
+ "Add attachment [/artifact/%!S|%h] to"
2071
+ " tech note [/technote/%h|%.10h]",
2072
+ zSrc, zName, zTarget, zTarget);
2073
+ }else{
2074
+ zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2075
+ zName, zTarget);
2076
+ }
2077
+ db_multi_exec("UPDATE event SET comment=%Q, type='e'"
2078
+ " WHERE objid=%Q",
2079
+ zComment, zAttachId);
2080
+ fossil_free(zComment);
2081
+ }
2082
+ db_finalize(&qatt);
20542083
}
20552084
if( p->type==CFTYPE_TICKET ){
20562085
char *zTag;
2086
+ Stmt qatt;
20572087
assert( manifest_crosslink_busy==1 );
20582088
zTag = mprintf("tkt-%s", p->zTicketUuid);
20592089
tag_insert(zTag, 1, 0, rid, p->rDate, rid);
20602090
fossil_free(zTag);
20612091
db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
20622092
p->zTicketUuid);
2093
+ /* Locate and update comment for any attachments */
2094
+ db_prepare(&qatt,
2095
+ "SELECT attachid, src, target, filename FROM attachment"
2096
+ " WHERE target=%Q",
2097
+ p->zTicketUuid
2098
+ );
2099
+ while( db_step(&qatt)==SQLITE_ROW ){
2100
+ const char *zAttachId = db_column_text(&qatt, 0);
2101
+ const char *zSrc = db_column_text(&qatt, 1);
2102
+ const char *zTarget = db_column_text(&qatt, 2);
2103
+ const char *zName = db_column_text(&qatt, 3);
2104
+ const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2105
+ char *zComment;
2106
+ if( isAdd ){
2107
+ zComment = mprintf(
2108
+ "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2109
+ zSrc, zName, zTarget, zTarget);
2110
+ }else{
2111
+ zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]",
2112
+ zName, zTarget, zTarget);
2113
+ }
2114
+ db_multi_exec("UPDATE event SET comment=%Q, type='t'"
2115
+ " WHERE objid=%Q",
2116
+ zComment, zAttachId);
2117
+ fossil_free(zComment);
2118
+ }
2119
+ db_finalize(&qatt);
20632120
}
20642121
if( p->type==CFTYPE_ATTACHMENT ){
20652122
char *zComment = 0;
20662123
const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
2067
- const char attachToType = fossil_is_uuid(p->zAttachTarget)
2068
- ? 't' /* attach to ticket */
2069
- : 'w' /* attach to wiki page */;
2124
+ /* We assume that we're attaching to a wiki page until we
2125
+ ** prove otherwise (which could on a later artifact if we
2126
+ ** process the attachment artifact before the artifact to
2127
+ ** which it is attached!) */
2128
+ char attachToType = 'w';
2129
+ if( fossil_is_uuid(p->zAttachTarget) ){
2130
+ if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
2131
+ p->zAttachTarget)
2132
+ ){
2133
+ attachToType = 't'; /* Attaching to known ticket */
2134
+ }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",
2135
+ p->zAttachTarget)
2136
+ ){
2137
+ attachToType = 'e'; /* Attaching to known tech note */
2138
+ }
2139
+ }
20702140
db_multi_exec(
20712141
"INSERT INTO attachment(attachid, mtime, src, target,"
20722142
"filename, comment, user)"
20732143
"VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
20742144
rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
@@ -2089,10 +2159,19 @@
20892159
p->zAttachSrc, p->zAttachName, p->zAttachTarget);
20902160
}else{
20912161
zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
20922162
p->zAttachName, p->zAttachTarget);
20932163
}
2164
+ }else if( 'e' == attachToType ){
2165
+ if( isAdd ){
2166
+ zComment = mprintf(
2167
+ "Add attachment [/artifact/%!S|%h] to tech note [/technote/%h|%.10h]",
2168
+ p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2169
+ }else{
2170
+ zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2171
+ p->zAttachName, p->zAttachTarget);
2172
+ }
20942173
}else{
20952174
if( isAdd ){
20962175
zComment = mprintf(
20972176
"Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
20982177
p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
20992178
--- src/manifest.c
+++ src/manifest.c
@@ -2008,10 +2008,11 @@
2008 char *zTag = mprintf("event-%s", p->zEventId);
2009 int tagid = tag_findid(zTag, 1);
2010 int prior, subsequent;
2011 int nWiki;
2012 char zLength[40];
 
2013 while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
2014 nWiki = strlen(p->zWiki);
2015 sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
2016 tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
2017 fossil_free(zTag);
@@ -2049,26 +2050,95 @@
2049 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
2050 p->rEventDate, rid, tagid, p->zUser, p->zComment,
2051 TAG_BGCOLOR, rid
2052 );
2053 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2054 }
2055 if( p->type==CFTYPE_TICKET ){
2056 char *zTag;
 
2057 assert( manifest_crosslink_busy==1 );
2058 zTag = mprintf("tkt-%s", p->zTicketUuid);
2059 tag_insert(zTag, 1, 0, rid, p->rDate, rid);
2060 fossil_free(zTag);
2061 db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
2062 p->zTicketUuid);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2063 }
2064 if( p->type==CFTYPE_ATTACHMENT ){
2065 char *zComment = 0;
2066 const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
2067 const char attachToType = fossil_is_uuid(p->zAttachTarget)
2068 ? 't' /* attach to ticket */
2069 : 'w' /* attach to wiki page */;
 
 
 
 
 
 
 
 
 
 
 
 
 
2070 db_multi_exec(
2071 "INSERT INTO attachment(attachid, mtime, src, target,"
2072 "filename, comment, user)"
2073 "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
2074 rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
@@ -2089,10 +2159,19 @@
2089 p->zAttachSrc, p->zAttachName, p->zAttachTarget);
2090 }else{
2091 zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
2092 p->zAttachName, p->zAttachTarget);
2093 }
 
 
 
 
 
 
 
 
 
2094 }else{
2095 if( isAdd ){
2096 zComment = mprintf(
2097 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2098 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2099
--- src/manifest.c
+++ src/manifest.c
@@ -2008,10 +2008,11 @@
2008 char *zTag = mprintf("event-%s", p->zEventId);
2009 int tagid = tag_findid(zTag, 1);
2010 int prior, subsequent;
2011 int nWiki;
2012 char zLength[40];
2013 Stmt qatt;
2014 while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
2015 nWiki = strlen(p->zWiki);
2016 sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
2017 tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
2018 fossil_free(zTag);
@@ -2049,26 +2050,95 @@
2050 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
2051 p->rEventDate, rid, tagid, p->zUser, p->zComment,
2052 TAG_BGCOLOR, rid
2053 );
2054 }
2055 /* Locate and update comment for any attachments */
2056 db_prepare(&qatt,
2057 "SELECT attachid, src, target, filename FROM attachment"
2058 " WHERE target=%Q",
2059 p->zEventId
2060 );
2061 while( db_step(&qatt)==SQLITE_ROW ){
2062 const char *zAttachId = db_column_text(&qatt, 0);
2063 const char *zSrc = db_column_text(&qatt, 1);
2064 const char *zTarget = db_column_text(&qatt, 2);
2065 const char *zName = db_column_text(&qatt, 3);
2066 const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2067 char *zComment;
2068 if( isAdd ){
2069 zComment = mprintf(
2070 "Add attachment [/artifact/%!S|%h] to"
2071 " tech note [/technote/%h|%.10h]",
2072 zSrc, zName, zTarget, zTarget);
2073 }else{
2074 zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2075 zName, zTarget);
2076 }
2077 db_multi_exec("UPDATE event SET comment=%Q, type='e'"
2078 " WHERE objid=%Q",
2079 zComment, zAttachId);
2080 fossil_free(zComment);
2081 }
2082 db_finalize(&qatt);
2083 }
2084 if( p->type==CFTYPE_TICKET ){
2085 char *zTag;
2086 Stmt qatt;
2087 assert( manifest_crosslink_busy==1 );
2088 zTag = mprintf("tkt-%s", p->zTicketUuid);
2089 tag_insert(zTag, 1, 0, rid, p->rDate, rid);
2090 fossil_free(zTag);
2091 db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
2092 p->zTicketUuid);
2093 /* Locate and update comment for any attachments */
2094 db_prepare(&qatt,
2095 "SELECT attachid, src, target, filename FROM attachment"
2096 " WHERE target=%Q",
2097 p->zTicketUuid
2098 );
2099 while( db_step(&qatt)==SQLITE_ROW ){
2100 const char *zAttachId = db_column_text(&qatt, 0);
2101 const char *zSrc = db_column_text(&qatt, 1);
2102 const char *zTarget = db_column_text(&qatt, 2);
2103 const char *zName = db_column_text(&qatt, 3);
2104 const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
2105 char *zComment;
2106 if( isAdd ){
2107 zComment = mprintf(
2108 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2109 zSrc, zName, zTarget, zTarget);
2110 }else{
2111 zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]",
2112 zName, zTarget, zTarget);
2113 }
2114 db_multi_exec("UPDATE event SET comment=%Q, type='t'"
2115 " WHERE objid=%Q",
2116 zComment, zAttachId);
2117 fossil_free(zComment);
2118 }
2119 db_finalize(&qatt);
2120 }
2121 if( p->type==CFTYPE_ATTACHMENT ){
2122 char *zComment = 0;
2123 const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
2124 /* We assume that we're attaching to a wiki page until we
2125 ** prove otherwise (which could on a later artifact if we
2126 ** process the attachment artifact before the artifact to
2127 ** which it is attached!) */
2128 char attachToType = 'w';
2129 if( fossil_is_uuid(p->zAttachTarget) ){
2130 if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
2131 p->zAttachTarget)
2132 ){
2133 attachToType = 't'; /* Attaching to known ticket */
2134 }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",
2135 p->zAttachTarget)
2136 ){
2137 attachToType = 'e'; /* Attaching to known tech note */
2138 }
2139 }
2140 db_multi_exec(
2141 "INSERT INTO attachment(attachid, mtime, src, target,"
2142 "filename, comment, user)"
2143 "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
2144 rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
@@ -2089,10 +2159,19 @@
2159 p->zAttachSrc, p->zAttachName, p->zAttachTarget);
2160 }else{
2161 zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
2162 p->zAttachName, p->zAttachTarget);
2163 }
2164 }else if( 'e' == attachToType ){
2165 if( isAdd ){
2166 zComment = mprintf(
2167 "Add attachment [/artifact/%!S|%h] to tech note [/technote/%h|%.10h]",
2168 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2169 }else{
2170 zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
2171 p->zAttachName, p->zAttachTarget);
2172 }
2173 }else{
2174 if( isAdd ){
2175 zComment = mprintf(
2176 "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
2177 p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
2178
+131 -62
--- src/wiki.c
+++ src/wiki.c
@@ -1137,36 +1137,44 @@
11371137
}
11381138
11391139
/*
11401140
** COMMAND: wiki*
11411141
**
1142
-** Usage: %fossil wiki (export|create|commit|list) WikiName
1143
-**
1144
-** Run various subcommands to work with wiki entries.
1145
-**
1146
-** %fossil wiki export PAGENAME ?FILE?
1147
-**
1148
-** Sends the latest version of the PAGENAME wiki
1149
-** entry to the given file or standard output.
1150
-**
1151
-** %fossil wiki commit PAGENAME ?FILE? [-mimetype TEXT-FORMAT]
1152
-**
1153
-** Commit changes to a wiki page from FILE or from standard
1154
-** input. The -mimetype (-M) flag specifies the mime type,
1155
-** defaulting to the type used by the previous version of
1156
-** the page or (for new pages) text/x-fossil-wiki.
1157
-**
1158
-** %fossil wiki create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]
1159
-**
1160
-** Create a new wiki page with initial content taken from
1161
-** FILE or from standard input.
1162
-**
1163
-** %fossil wiki list
1164
-** %fossil wiki ls
1165
-**
1166
-** Lists all wiki entries, one per line, ordered
1167
-** case-insensitively by name.
1142
+** Usage: ../fossil wiki (export|create|commit|list) WikiName
1143
+**
1144
+** Run various subcommands to work with wiki entries or tech notes.
1145
+**
1146
+** ../fossil wiki export ?PAGENAME? ?FILE? [-t|--technote DATETIME ]
1147
+**
1148
+** Sends the latest version of either the PAGENAME wiki entry
1149
+** or the DATETIME tech note to the given file or standard
1150
+** output. One of PAGENAME or DATETIME must be specified.
1151
+**
1152
+** ../fossil wiki (create|commit) PAGENAME ?FILE? ?OPTIONS?
1153
+**
1154
+** Create a new or commit changes to an existing wiki page or
1155
+** technote from FILE or from standard input.
1156
+**
1157
+** Options:
1158
+** -M|--mimetype TEXT-FORMAT The mime type of the update defaulting
1159
+** defaulting to the type used by the
1160
+** previous version of the page or (for
1161
+** new pages) text/x-fossil-wiki.
1162
+** -t|--technote DATETIME Specifies the timestamp of the technote
1163
+** to be created or updated.
1164
+** --technote-tags TAGS The set of tags for a technote.
1165
+** --technote-bgcolor COLOR The color used for the technote on the
1166
+** timeline.
1167
+**
1168
+** ../fossil wiki list ?--technote?
1169
+** ../fossil wiki ls ?--technote?
1170
+**
1171
+** Lists all wiki entries, one per line, ordered
1172
+** case-insensitively by name. The --technote flag
1173
+** specifies that technotes will be listed instead of
1174
+** the wiki entries, which will be listed in order
1175
+** timestamp.
11681176
**
11691177
*/
11701178
void wiki_cmd(void){
11711179
int n;
11721180
db_find_and_open_repository(0, 0);
@@ -1179,33 +1187,54 @@
11791187
}
11801188
11811189
if( strncmp(g.argv[2],"export",n)==0 ){
11821190
const char *zPageName; /* Name of the wiki page to export */
11831191
const char *zFile; /* Name of the output file (0=stdout) */
1192
+ const char *zETime; /* The name of the technote to export */
11841193
int rid; /* Artifact ID of the wiki page */
11851194
int i; /* Loop counter */
11861195
char *zBody = 0; /* Wiki page content */
11871196
Blob body; /* Wiki page content */
11881197
Manifest *pWiki = 0; /* Parsed wiki page content */
1189
- if( (g.argc!=4) && (g.argc!=5) ){
1190
- usage("export PAGENAME ?FILE?");
1191
- }
1192
- zPageName = g.argv[3];
1193
- rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1194
- " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1195
- " ORDER BY x.mtime DESC LIMIT 1",
1196
- zPageName
1197
- );
1198
- if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
1199
- zBody = pWiki->zWiki;
1200
- }
1201
- if( zBody==0 ){
1202
- fossil_fatal("wiki page [%s] not found",zPageName);
1198
+
1199
+ zETime = find_option("technote","t",1);
1200
+ if( !zETime ){
1201
+ if( (g.argc!=4) && (g.argc!=5) ){
1202
+ usage("export PAGENAME ?FILE?");
1203
+ }
1204
+ zPageName = g.argv[3];
1205
+ rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1206
+ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1207
+ " ORDER BY x.mtime DESC LIMIT 1",
1208
+ zPageName
1209
+ );
1210
+ if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
1211
+ zBody = pWiki->zWiki;
1212
+ }
1213
+ if( zBody==0 ){
1214
+ fossil_fatal("wiki page [%s] not found",zPageName);
1215
+ }
1216
+ zFile = (g.argc==4) ? "-" : g.argv[4];
1217
+ }else{
1218
+ if( (g.argc!=3) && (g.argc!=4) ){
1219
+ usage("export ?FILE? --technote DATETIME");
1220
+ }
1221
+ rid = db_int(0, "SELECT objid FROM event"
1222
+ " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1223
+ " ORDER BY mtime DESC LIMIT 1",
1224
+ zETime
1225
+ );
1226
+ if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
1227
+ zBody = pWiki->zWiki;
1228
+ }
1229
+ if( zBody==0 ){
1230
+ fossil_fatal("technote not found");
1231
+ }
1232
+ zFile = (g.argc==3) ? "-" : g.argv[3];
12031233
}
12041234
for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
12051235
zBody[i] = 0;
1206
- zFile = (g.argc==4) ? "-" : g.argv[4];
12071236
blob_init(&body, zBody, -1);
12081237
blob_append(&body, "\n", 1);
12091238
blob_write_to_file(&body, zFile);
12101239
blob_reset(&body);
12111240
manifest_destroy(pWiki);
@@ -1215,37 +1244,70 @@
12151244
const char *zPageName; /* page name */
12161245
Blob content; /* Input content */
12171246
int rid;
12181247
Manifest *pWiki = 0; /* Parsed wiki page content */
12191248
const char *zMimeType = find_option("mimetype", "M", 1);
1249
+ const char *zETime = find_option("technote", "t", 1);
1250
+ const char *zTags = find_option("technote-tags", NULL, 1);
1251
+ const char *zClr = find_option("technote-bgcolor", NULL, 1);
12201252
if( g.argc!=4 && g.argc!=5 ){
1221
- usage("commit|create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]");
1253
+ usage("commit|create PAGENAME ?FILE? [--mimetype TEXT-FORMAT]"
1254
+ " [--technote DATETIME] [--technote-tags TAGS]"
1255
+ " [--technote-bgcolor COLOR]");
12221256
}
12231257
zPageName = g.argv[3];
12241258
if( g.argc==4 ){
12251259
blob_read_from_channel(&content, stdin, -1);
12261260
}else{
12271261
blob_read_from_file(&content, g.argv[4]);
12281262
}
12291263
if(!zMimeType || !*zMimeType){
12301264
/* Try to deduce the mime type based on the prior version. */
1231
- rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1232
- " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1233
- " ORDER BY x.mtime DESC LIMIT 1",
1234
- zPageName
1235
- );
1236
- if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1237
- && (pWiki->zMimetype && *pWiki->zMimetype)){
1238
- zMimeType = pWiki->zMimetype;
1239
- }
1240
- }
1241
- if( g.argv[2][1]=='r' ){
1242
- wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1243
- fossil_print("Created new wiki page %s.\n", zPageName);
1244
- }else{
1245
- wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1246
- fossil_print("Updated wiki page %s.\n", zPageName);
1265
+ if ( !zETime ){
1266
+ rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1267
+ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1268
+ " ORDER BY x.mtime DESC LIMIT 1",
1269
+ zPageName
1270
+ );
1271
+ if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1272
+ && (pWiki->zMimetype && *pWiki->zMimetype)){
1273
+ zMimeType = pWiki->zMimetype;
1274
+ }
1275
+ }else{
1276
+ rid = db_int(0, "SELECT objid FROM event"
1277
+ " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1278
+ " ORDER BY mtime DESC LIMIT 1",
1279
+ zPageName
1280
+ );
1281
+ if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0
1282
+ && (pWiki->zMimetype && *pWiki->zMimetype)){
1283
+ zMimeType = pWiki->zMimetype;
1284
+ }
1285
+ }
1286
+ }
1287
+ if( !zETime ){
1288
+ if( g.argv[2][1]=='r' ){
1289
+ wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1290
+ fossil_print("Created new wiki page %s.\n", zPageName);
1291
+ }else{
1292
+ wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1293
+ fossil_print("Updated wiki page %s.\n", zPageName);
1294
+ }
1295
+ }else{
1296
+ char *zMETime; /* Normalized, mutable version of zETime */
1297
+ zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))",
1298
+ zETime);
1299
+ if( g.argv[2][1]=='r' ){
1300
+ event_cmd_commit(zMETime, 1, &content, zMimeType, zPageName,
1301
+ zTags, zClr);
1302
+ fossil_print("Created new tech note %s.\n", zMETime);
1303
+ }else{
1304
+ event_cmd_commit(zMETime, 0, &content, zMimeType, zPageName,
1305
+ zTags, zClr);
1306
+ fossil_print("Updated tech note %s.\n", zMETime);
1307
+ }
1308
+ free(zMETime);
12471309
}
12481310
manifest_destroy(pWiki);
12491311
blob_reset(&content);
12501312
}else if( strncmp(g.argv[2],"delete",n)==0 ){
12511313
if( g.argc!=5 ){
@@ -1253,14 +1315,21 @@
12531315
}
12541316
fossil_fatal("delete not yet implemented.");
12551317
}else if(( strncmp(g.argv[2],"list",n)==0 )
12561318
|| ( strncmp(g.argv[2],"ls",n)==0 )){
12571319
Stmt q;
1258
- db_prepare(&q,
1259
- "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1260
- " ORDER BY lower(tagname) /*sort*/"
1261
- );
1320
+ if ( !find_option("technote","t",0) ){
1321
+ db_prepare(&q,
1322
+ "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1323
+ " ORDER BY lower(tagname) /*sort*/"
1324
+ );
1325
+ }else{
1326
+ db_prepare(&q,
1327
+ "SELECT datetime(mtime) FROM event WHERE type='e'"
1328
+ " ORDER BY mtime /*sort*/"
1329
+ );
1330
+ }
12621331
while( db_step(&q)==SQLITE_ROW ){
12631332
const char *zName = db_column_text(&q, 0);
12641333
fossil_print( "%s\n",zName);
12651334
}
12661335
db_finalize(&q);
12671336
--- src/wiki.c
+++ src/wiki.c
@@ -1137,36 +1137,44 @@
1137 }
1138
1139 /*
1140 ** COMMAND: wiki*
1141 **
1142 ** Usage: %fossil wiki (export|create|commit|list) WikiName
1143 **
1144 ** Run various subcommands to work with wiki entries.
1145 **
1146 ** %fossil wiki export PAGENAME ?FILE?
1147 **
1148 ** Sends the latest version of the PAGENAME wiki
1149 ** entry to the given file or standard output.
1150 **
1151 ** %fossil wiki commit PAGENAME ?FILE? [-mimetype TEXT-FORMAT]
1152 **
1153 ** Commit changes to a wiki page from FILE or from standard
1154 ** input. The -mimetype (-M) flag specifies the mime type,
1155 ** defaulting to the type used by the previous version of
1156 ** the page or (for new pages) text/x-fossil-wiki.
1157 **
1158 ** %fossil wiki create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]
1159 **
1160 ** Create a new wiki page with initial content taken from
1161 ** FILE or from standard input.
1162 **
1163 ** %fossil wiki list
1164 ** %fossil wiki ls
1165 **
1166 ** Lists all wiki entries, one per line, ordered
1167 ** case-insensitively by name.
 
 
 
 
 
 
 
 
1168 **
1169 */
1170 void wiki_cmd(void){
1171 int n;
1172 db_find_and_open_repository(0, 0);
@@ -1179,33 +1187,54 @@
1179 }
1180
1181 if( strncmp(g.argv[2],"export",n)==0 ){
1182 const char *zPageName; /* Name of the wiki page to export */
1183 const char *zFile; /* Name of the output file (0=stdout) */
 
1184 int rid; /* Artifact ID of the wiki page */
1185 int i; /* Loop counter */
1186 char *zBody = 0; /* Wiki page content */
1187 Blob body; /* Wiki page content */
1188 Manifest *pWiki = 0; /* Parsed wiki page content */
1189 if( (g.argc!=4) && (g.argc!=5) ){
1190 usage("export PAGENAME ?FILE?");
1191 }
1192 zPageName = g.argv[3];
1193 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1194 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1195 " ORDER BY x.mtime DESC LIMIT 1",
1196 zPageName
1197 );
1198 if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
1199 zBody = pWiki->zWiki;
1200 }
1201 if( zBody==0 ){
1202 fossil_fatal("wiki page [%s] not found",zPageName);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1203 }
1204 for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
1205 zBody[i] = 0;
1206 zFile = (g.argc==4) ? "-" : g.argv[4];
1207 blob_init(&body, zBody, -1);
1208 blob_append(&body, "\n", 1);
1209 blob_write_to_file(&body, zFile);
1210 blob_reset(&body);
1211 manifest_destroy(pWiki);
@@ -1215,37 +1244,70 @@
1215 const char *zPageName; /* page name */
1216 Blob content; /* Input content */
1217 int rid;
1218 Manifest *pWiki = 0; /* Parsed wiki page content */
1219 const char *zMimeType = find_option("mimetype", "M", 1);
 
 
 
1220 if( g.argc!=4 && g.argc!=5 ){
1221 usage("commit|create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]");
 
 
1222 }
1223 zPageName = g.argv[3];
1224 if( g.argc==4 ){
1225 blob_read_from_channel(&content, stdin, -1);
1226 }else{
1227 blob_read_from_file(&content, g.argv[4]);
1228 }
1229 if(!zMimeType || !*zMimeType){
1230 /* Try to deduce the mime type based on the prior version. */
1231 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1232 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1233 " ORDER BY x.mtime DESC LIMIT 1",
1234 zPageName
1235 );
1236 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1237 && (pWiki->zMimetype && *pWiki->zMimetype)){
1238 zMimeType = pWiki->zMimetype;
1239 }
1240 }
1241 if( g.argv[2][1]=='r' ){
1242 wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1243 fossil_print("Created new wiki page %s.\n", zPageName);
1244 }else{
1245 wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1246 fossil_print("Updated wiki page %s.\n", zPageName);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1247 }
1248 manifest_destroy(pWiki);
1249 blob_reset(&content);
1250 }else if( strncmp(g.argv[2],"delete",n)==0 ){
1251 if( g.argc!=5 ){
@@ -1253,14 +1315,21 @@
1253 }
1254 fossil_fatal("delete not yet implemented.");
1255 }else if(( strncmp(g.argv[2],"list",n)==0 )
1256 || ( strncmp(g.argv[2],"ls",n)==0 )){
1257 Stmt q;
1258 db_prepare(&q,
1259 "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1260 " ORDER BY lower(tagname) /*sort*/"
1261 );
 
 
 
 
 
 
 
1262 while( db_step(&q)==SQLITE_ROW ){
1263 const char *zName = db_column_text(&q, 0);
1264 fossil_print( "%s\n",zName);
1265 }
1266 db_finalize(&q);
1267
--- src/wiki.c
+++ src/wiki.c
@@ -1137,36 +1137,44 @@
1137 }
1138
1139 /*
1140 ** COMMAND: wiki*
1141 **
1142 ** Usage: ../fossil wiki (export|create|commit|list) WikiName
1143 **
1144 ** Run various subcommands to work with wiki entries or tech notes.
1145 **
1146 ** ../fossil wiki export ?PAGENAME? ?FILE? [-t|--technote DATETIME ]
1147 **
1148 ** Sends the latest version of either the PAGENAME wiki entry
1149 ** or the DATETIME tech note to the given file or standard
1150 ** output. One of PAGENAME or DATETIME must be specified.
1151 **
1152 ** ../fossil wiki (create|commit) PAGENAME ?FILE? ?OPTIONS?
1153 **
1154 ** Create a new or commit changes to an existing wiki page or
1155 ** technote from FILE or from standard input.
1156 **
1157 ** Options:
1158 ** -M|--mimetype TEXT-FORMAT The mime type of the update defaulting
1159 ** defaulting to the type used by the
1160 ** previous version of the page or (for
1161 ** new pages) text/x-fossil-wiki.
1162 ** -t|--technote DATETIME Specifies the timestamp of the technote
1163 ** to be created or updated.
1164 ** --technote-tags TAGS The set of tags for a technote.
1165 ** --technote-bgcolor COLOR The color used for the technote on the
1166 ** timeline.
1167 **
1168 ** ../fossil wiki list ?--technote?
1169 ** ../fossil wiki ls ?--technote?
1170 **
1171 ** Lists all wiki entries, one per line, ordered
1172 ** case-insensitively by name. The --technote flag
1173 ** specifies that technotes will be listed instead of
1174 ** the wiki entries, which will be listed in order
1175 ** timestamp.
1176 **
1177 */
1178 void wiki_cmd(void){
1179 int n;
1180 db_find_and_open_repository(0, 0);
@@ -1179,33 +1187,54 @@
1187 }
1188
1189 if( strncmp(g.argv[2],"export",n)==0 ){
1190 const char *zPageName; /* Name of the wiki page to export */
1191 const char *zFile; /* Name of the output file (0=stdout) */
1192 const char *zETime; /* The name of the technote to export */
1193 int rid; /* Artifact ID of the wiki page */
1194 int i; /* Loop counter */
1195 char *zBody = 0; /* Wiki page content */
1196 Blob body; /* Wiki page content */
1197 Manifest *pWiki = 0; /* Parsed wiki page content */
1198
1199 zETime = find_option("technote","t",1);
1200 if( !zETime ){
1201 if( (g.argc!=4) && (g.argc!=5) ){
1202 usage("export PAGENAME ?FILE?");
1203 }
1204 zPageName = g.argv[3];
1205 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1206 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1207 " ORDER BY x.mtime DESC LIMIT 1",
1208 zPageName
1209 );
1210 if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
1211 zBody = pWiki->zWiki;
1212 }
1213 if( zBody==0 ){
1214 fossil_fatal("wiki page [%s] not found",zPageName);
1215 }
1216 zFile = (g.argc==4) ? "-" : g.argv[4];
1217 }else{
1218 if( (g.argc!=3) && (g.argc!=4) ){
1219 usage("export ?FILE? --technote DATETIME");
1220 }
1221 rid = db_int(0, "SELECT objid FROM event"
1222 " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1223 " ORDER BY mtime DESC LIMIT 1",
1224 zETime
1225 );
1226 if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
1227 zBody = pWiki->zWiki;
1228 }
1229 if( zBody==0 ){
1230 fossil_fatal("technote not found");
1231 }
1232 zFile = (g.argc==3) ? "-" : g.argv[3];
1233 }
1234 for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
1235 zBody[i] = 0;
 
1236 blob_init(&body, zBody, -1);
1237 blob_append(&body, "\n", 1);
1238 blob_write_to_file(&body, zFile);
1239 blob_reset(&body);
1240 manifest_destroy(pWiki);
@@ -1215,37 +1244,70 @@
1244 const char *zPageName; /* page name */
1245 Blob content; /* Input content */
1246 int rid;
1247 Manifest *pWiki = 0; /* Parsed wiki page content */
1248 const char *zMimeType = find_option("mimetype", "M", 1);
1249 const char *zETime = find_option("technote", "t", 1);
1250 const char *zTags = find_option("technote-tags", NULL, 1);
1251 const char *zClr = find_option("technote-bgcolor", NULL, 1);
1252 if( g.argc!=4 && g.argc!=5 ){
1253 usage("commit|create PAGENAME ?FILE? [--mimetype TEXT-FORMAT]"
1254 " [--technote DATETIME] [--technote-tags TAGS]"
1255 " [--technote-bgcolor COLOR]");
1256 }
1257 zPageName = g.argv[3];
1258 if( g.argc==4 ){
1259 blob_read_from_channel(&content, stdin, -1);
1260 }else{
1261 blob_read_from_file(&content, g.argv[4]);
1262 }
1263 if(!zMimeType || !*zMimeType){
1264 /* Try to deduce the mime type based on the prior version. */
1265 if ( !zETime ){
1266 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1267 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1268 " ORDER BY x.mtime DESC LIMIT 1",
1269 zPageName
1270 );
1271 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1272 && (pWiki->zMimetype && *pWiki->zMimetype)){
1273 zMimeType = pWiki->zMimetype;
1274 }
1275 }else{
1276 rid = db_int(0, "SELECT objid FROM event"
1277 " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1278 " ORDER BY mtime DESC LIMIT 1",
1279 zPageName
1280 );
1281 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0
1282 && (pWiki->zMimetype && *pWiki->zMimetype)){
1283 zMimeType = pWiki->zMimetype;
1284 }
1285 }
1286 }
1287 if( !zETime ){
1288 if( g.argv[2][1]=='r' ){
1289 wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1290 fossil_print("Created new wiki page %s.\n", zPageName);
1291 }else{
1292 wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1293 fossil_print("Updated wiki page %s.\n", zPageName);
1294 }
1295 }else{
1296 char *zMETime; /* Normalized, mutable version of zETime */
1297 zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))",
1298 zETime);
1299 if( g.argv[2][1]=='r' ){
1300 event_cmd_commit(zMETime, 1, &content, zMimeType, zPageName,
1301 zTags, zClr);
1302 fossil_print("Created new tech note %s.\n", zMETime);
1303 }else{
1304 event_cmd_commit(zMETime, 0, &content, zMimeType, zPageName,
1305 zTags, zClr);
1306 fossil_print("Updated tech note %s.\n", zMETime);
1307 }
1308 free(zMETime);
1309 }
1310 manifest_destroy(pWiki);
1311 blob_reset(&content);
1312 }else if( strncmp(g.argv[2],"delete",n)==0 ){
1313 if( g.argc!=5 ){
@@ -1253,14 +1315,21 @@
1315 }
1316 fossil_fatal("delete not yet implemented.");
1317 }else if(( strncmp(g.argv[2],"list",n)==0 )
1318 || ( strncmp(g.argv[2],"ls",n)==0 )){
1319 Stmt q;
1320 if ( !find_option("technote","t",0) ){
1321 db_prepare(&q,
1322 "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1323 " ORDER BY lower(tagname) /*sort*/"
1324 );
1325 }else{
1326 db_prepare(&q,
1327 "SELECT datetime(mtime) FROM event WHERE type='e'"
1328 " ORDER BY mtime /*sort*/"
1329 );
1330 }
1331 while( db_step(&q)==SQLITE_ROW ){
1332 const char *zName = db_column_text(&q, 0);
1333 fossil_print( "%s\n",zName);
1334 }
1335 db_finalize(&q);
1336
+131 -62
--- src/wiki.c
+++ src/wiki.c
@@ -1137,36 +1137,44 @@
11371137
}
11381138
11391139
/*
11401140
** COMMAND: wiki*
11411141
**
1142
-** Usage: %fossil wiki (export|create|commit|list) WikiName
1143
-**
1144
-** Run various subcommands to work with wiki entries.
1145
-**
1146
-** %fossil wiki export PAGENAME ?FILE?
1147
-**
1148
-** Sends the latest version of the PAGENAME wiki
1149
-** entry to the given file or standard output.
1150
-**
1151
-** %fossil wiki commit PAGENAME ?FILE? [-mimetype TEXT-FORMAT]
1152
-**
1153
-** Commit changes to a wiki page from FILE or from standard
1154
-** input. The -mimetype (-M) flag specifies the mime type,
1155
-** defaulting to the type used by the previous version of
1156
-** the page or (for new pages) text/x-fossil-wiki.
1157
-**
1158
-** %fossil wiki create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]
1159
-**
1160
-** Create a new wiki page with initial content taken from
1161
-** FILE or from standard input.
1162
-**
1163
-** %fossil wiki list
1164
-** %fossil wiki ls
1165
-**
1166
-** Lists all wiki entries, one per line, ordered
1167
-** case-insensitively by name.
1142
+** Usage: ../fossil wiki (export|create|commit|list) WikiName
1143
+**
1144
+** Run various subcommands to work with wiki entries or tech notes.
1145
+**
1146
+** ../fossil wiki export ?PAGENAME? ?FILE? [-t|--technote DATETIME ]
1147
+**
1148
+** Sends the latest version of either the PAGENAME wiki entry
1149
+** or the DATETIME tech note to the given file or standard
1150
+** output. One of PAGENAME or DATETIME must be specified.
1151
+**
1152
+** ../fossil wiki (create|commit) PAGENAME ?FILE? ?OPTIONS?
1153
+**
1154
+** Create a new or commit changes to an existing wiki page or
1155
+** technote from FILE or from standard input.
1156
+**
1157
+** Options:
1158
+** -M|--mimetype TEXT-FORMAT The mime type of the update defaulting
1159
+** defaulting to the type used by the
1160
+** previous version of the page or (for
1161
+** new pages) text/x-fossil-wiki.
1162
+** -t|--technote DATETIME Specifies the timestamp of the technote
1163
+** to be created or updated.
1164
+** --technote-tags TAGS The set of tags for a technote.
1165
+** --technote-bgcolor COLOR The color used for the technote on the
1166
+** timeline.
1167
+**
1168
+** ../fossil wiki list ?--technote?
1169
+** ../fossil wiki ls ?--technote?
1170
+**
1171
+** Lists all wiki entries, one per line, ordered
1172
+** case-insensitively by name. The --technote flag
1173
+** specifies that technotes will be listed instead of
1174
+** the wiki entries, which will be listed in order
1175
+** timestamp.
11681176
**
11691177
*/
11701178
void wiki_cmd(void){
11711179
int n;
11721180
db_find_and_open_repository(0, 0);
@@ -1179,33 +1187,54 @@
11791187
}
11801188
11811189
if( strncmp(g.argv[2],"export",n)==0 ){
11821190
const char *zPageName; /* Name of the wiki page to export */
11831191
const char *zFile; /* Name of the output file (0=stdout) */
1192
+ const char *zETime; /* The name of the technote to export */
11841193
int rid; /* Artifact ID of the wiki page */
11851194
int i; /* Loop counter */
11861195
char *zBody = 0; /* Wiki page content */
11871196
Blob body; /* Wiki page content */
11881197
Manifest *pWiki = 0; /* Parsed wiki page content */
1189
- if( (g.argc!=4) && (g.argc!=5) ){
1190
- usage("export PAGENAME ?FILE?");
1191
- }
1192
- zPageName = g.argv[3];
1193
- rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1194
- " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1195
- " ORDER BY x.mtime DESC LIMIT 1",
1196
- zPageName
1197
- );
1198
- if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
1199
- zBody = pWiki->zWiki;
1200
- }
1201
- if( zBody==0 ){
1202
- fossil_fatal("wiki page [%s] not found",zPageName);
1198
+
1199
+ zETime = find_option("technote","t",1);
1200
+ if( !zETime ){
1201
+ if( (g.argc!=4) && (g.argc!=5) ){
1202
+ usage("export PAGENAME ?FILE?");
1203
+ }
1204
+ zPageName = g.argv[3];
1205
+ rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1206
+ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1207
+ " ORDER BY x.mtime DESC LIMIT 1",
1208
+ zPageName
1209
+ );
1210
+ if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
1211
+ zBody = pWiki->zWiki;
1212
+ }
1213
+ if( zBody==0 ){
1214
+ fossil_fatal("wiki page [%s] not found",zPageName);
1215
+ }
1216
+ zFile = (g.argc==4) ? "-" : g.argv[4];
1217
+ }else{
1218
+ if( (g.argc!=3) && (g.argc!=4) ){
1219
+ usage("export ?FILE? --technote DATETIME");
1220
+ }
1221
+ rid = db_int(0, "SELECT objid FROM event"
1222
+ " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1223
+ " ORDER BY mtime DESC LIMIT 1",
1224
+ zETime
1225
+ );
1226
+ if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
1227
+ zBody = pWiki->zWiki;
1228
+ }
1229
+ if( zBody==0 ){
1230
+ fossil_fatal("technote not found");
1231
+ }
1232
+ zFile = (g.argc==3) ? "-" : g.argv[3];
12031233
}
12041234
for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
12051235
zBody[i] = 0;
1206
- zFile = (g.argc==4) ? "-" : g.argv[4];
12071236
blob_init(&body, zBody, -1);
12081237
blob_append(&body, "\n", 1);
12091238
blob_write_to_file(&body, zFile);
12101239
blob_reset(&body);
12111240
manifest_destroy(pWiki);
@@ -1215,37 +1244,70 @@
12151244
const char *zPageName; /* page name */
12161245
Blob content; /* Input content */
12171246
int rid;
12181247
Manifest *pWiki = 0; /* Parsed wiki page content */
12191248
const char *zMimeType = find_option("mimetype", "M", 1);
1249
+ const char *zETime = find_option("technote", "t", 1);
1250
+ const char *zTags = find_option("technote-tags", NULL, 1);
1251
+ const char *zClr = find_option("technote-bgcolor", NULL, 1);
12201252
if( g.argc!=4 && g.argc!=5 ){
1221
- usage("commit|create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]");
1253
+ usage("commit|create PAGENAME ?FILE? [--mimetype TEXT-FORMAT]"
1254
+ " [--technote DATETIME] [--technote-tags TAGS]"
1255
+ " [--technote-bgcolor COLOR]");
12221256
}
12231257
zPageName = g.argv[3];
12241258
if( g.argc==4 ){
12251259
blob_read_from_channel(&content, stdin, -1);
12261260
}else{
12271261
blob_read_from_file(&content, g.argv[4]);
12281262
}
12291263
if(!zMimeType || !*zMimeType){
12301264
/* Try to deduce the mime type based on the prior version. */
1231
- rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1232
- " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1233
- " ORDER BY x.mtime DESC LIMIT 1",
1234
- zPageName
1235
- );
1236
- if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1237
- && (pWiki->zMimetype && *pWiki->zMimetype)){
1238
- zMimeType = pWiki->zMimetype;
1239
- }
1240
- }
1241
- if( g.argv[2][1]=='r' ){
1242
- wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1243
- fossil_print("Created new wiki page %s.\n", zPageName);
1244
- }else{
1245
- wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1246
- fossil_print("Updated wiki page %s.\n", zPageName);
1265
+ if ( !zETime ){
1266
+ rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1267
+ " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1268
+ " ORDER BY x.mtime DESC LIMIT 1",
1269
+ zPageName
1270
+ );
1271
+ if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1272
+ && (pWiki->zMimetype && *pWiki->zMimetype)){
1273
+ zMimeType = pWiki->zMimetype;
1274
+ }
1275
+ }else{
1276
+ rid = db_int(0, "SELECT objid FROM event"
1277
+ " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1278
+ " ORDER BY mtime DESC LIMIT 1",
1279
+ zPageName
1280
+ );
1281
+ if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0
1282
+ && (pWiki->zMimetype && *pWiki->zMimetype)){
1283
+ zMimeType = pWiki->zMimetype;
1284
+ }
1285
+ }
1286
+ }
1287
+ if( !zETime ){
1288
+ if( g.argv[2][1]=='r' ){
1289
+ wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1290
+ fossil_print("Created new wiki page %s.\n", zPageName);
1291
+ }else{
1292
+ wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1293
+ fossil_print("Updated wiki page %s.\n", zPageName);
1294
+ }
1295
+ }else{
1296
+ char *zMETime; /* Normalized, mutable version of zETime */
1297
+ zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))",
1298
+ zETime);
1299
+ if( g.argv[2][1]=='r' ){
1300
+ event_cmd_commit(zMETime, 1, &content, zMimeType, zPageName,
1301
+ zTags, zClr);
1302
+ fossil_print("Created new tech note %s.\n", zMETime);
1303
+ }else{
1304
+ event_cmd_commit(zMETime, 0, &content, zMimeType, zPageName,
1305
+ zTags, zClr);
1306
+ fossil_print("Updated tech note %s.\n", zMETime);
1307
+ }
1308
+ free(zMETime);
12471309
}
12481310
manifest_destroy(pWiki);
12491311
blob_reset(&content);
12501312
}else if( strncmp(g.argv[2],"delete",n)==0 ){
12511313
if( g.argc!=5 ){
@@ -1253,14 +1315,21 @@
12531315
}
12541316
fossil_fatal("delete not yet implemented.");
12551317
}else if(( strncmp(g.argv[2],"list",n)==0 )
12561318
|| ( strncmp(g.argv[2],"ls",n)==0 )){
12571319
Stmt q;
1258
- db_prepare(&q,
1259
- "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1260
- " ORDER BY lower(tagname) /*sort*/"
1261
- );
1320
+ if ( !find_option("technote","t",0) ){
1321
+ db_prepare(&q,
1322
+ "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1323
+ " ORDER BY lower(tagname) /*sort*/"
1324
+ );
1325
+ }else{
1326
+ db_prepare(&q,
1327
+ "SELECT datetime(mtime) FROM event WHERE type='e'"
1328
+ " ORDER BY mtime /*sort*/"
1329
+ );
1330
+ }
12621331
while( db_step(&q)==SQLITE_ROW ){
12631332
const char *zName = db_column_text(&q, 0);
12641333
fossil_print( "%s\n",zName);
12651334
}
12661335
db_finalize(&q);
12671336
--- src/wiki.c
+++ src/wiki.c
@@ -1137,36 +1137,44 @@
1137 }
1138
1139 /*
1140 ** COMMAND: wiki*
1141 **
1142 ** Usage: %fossil wiki (export|create|commit|list) WikiName
1143 **
1144 ** Run various subcommands to work with wiki entries.
1145 **
1146 ** %fossil wiki export PAGENAME ?FILE?
1147 **
1148 ** Sends the latest version of the PAGENAME wiki
1149 ** entry to the given file or standard output.
1150 **
1151 ** %fossil wiki commit PAGENAME ?FILE? [-mimetype TEXT-FORMAT]
1152 **
1153 ** Commit changes to a wiki page from FILE or from standard
1154 ** input. The -mimetype (-M) flag specifies the mime type,
1155 ** defaulting to the type used by the previous version of
1156 ** the page or (for new pages) text/x-fossil-wiki.
1157 **
1158 ** %fossil wiki create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]
1159 **
1160 ** Create a new wiki page with initial content taken from
1161 ** FILE or from standard input.
1162 **
1163 ** %fossil wiki list
1164 ** %fossil wiki ls
1165 **
1166 ** Lists all wiki entries, one per line, ordered
1167 ** case-insensitively by name.
 
 
 
 
 
 
 
 
1168 **
1169 */
1170 void wiki_cmd(void){
1171 int n;
1172 db_find_and_open_repository(0, 0);
@@ -1179,33 +1187,54 @@
1179 }
1180
1181 if( strncmp(g.argv[2],"export",n)==0 ){
1182 const char *zPageName; /* Name of the wiki page to export */
1183 const char *zFile; /* Name of the output file (0=stdout) */
 
1184 int rid; /* Artifact ID of the wiki page */
1185 int i; /* Loop counter */
1186 char *zBody = 0; /* Wiki page content */
1187 Blob body; /* Wiki page content */
1188 Manifest *pWiki = 0; /* Parsed wiki page content */
1189 if( (g.argc!=4) && (g.argc!=5) ){
1190 usage("export PAGENAME ?FILE?");
1191 }
1192 zPageName = g.argv[3];
1193 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1194 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1195 " ORDER BY x.mtime DESC LIMIT 1",
1196 zPageName
1197 );
1198 if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
1199 zBody = pWiki->zWiki;
1200 }
1201 if( zBody==0 ){
1202 fossil_fatal("wiki page [%s] not found",zPageName);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1203 }
1204 for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
1205 zBody[i] = 0;
1206 zFile = (g.argc==4) ? "-" : g.argv[4];
1207 blob_init(&body, zBody, -1);
1208 blob_append(&body, "\n", 1);
1209 blob_write_to_file(&body, zFile);
1210 blob_reset(&body);
1211 manifest_destroy(pWiki);
@@ -1215,37 +1244,70 @@
1215 const char *zPageName; /* page name */
1216 Blob content; /* Input content */
1217 int rid;
1218 Manifest *pWiki = 0; /* Parsed wiki page content */
1219 const char *zMimeType = find_option("mimetype", "M", 1);
 
 
 
1220 if( g.argc!=4 && g.argc!=5 ){
1221 usage("commit|create PAGENAME ?FILE? [-mimetype TEXT-FORMAT]");
 
 
1222 }
1223 zPageName = g.argv[3];
1224 if( g.argc==4 ){
1225 blob_read_from_channel(&content, stdin, -1);
1226 }else{
1227 blob_read_from_file(&content, g.argv[4]);
1228 }
1229 if(!zMimeType || !*zMimeType){
1230 /* Try to deduce the mime type based on the prior version. */
1231 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1232 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1233 " ORDER BY x.mtime DESC LIMIT 1",
1234 zPageName
1235 );
1236 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1237 && (pWiki->zMimetype && *pWiki->zMimetype)){
1238 zMimeType = pWiki->zMimetype;
1239 }
1240 }
1241 if( g.argv[2][1]=='r' ){
1242 wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1243 fossil_print("Created new wiki page %s.\n", zPageName);
1244 }else{
1245 wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1246 fossil_print("Updated wiki page %s.\n", zPageName);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1247 }
1248 manifest_destroy(pWiki);
1249 blob_reset(&content);
1250 }else if( strncmp(g.argv[2],"delete",n)==0 ){
1251 if( g.argc!=5 ){
@@ -1253,14 +1315,21 @@
1253 }
1254 fossil_fatal("delete not yet implemented.");
1255 }else if(( strncmp(g.argv[2],"list",n)==0 )
1256 || ( strncmp(g.argv[2],"ls",n)==0 )){
1257 Stmt q;
1258 db_prepare(&q,
1259 "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1260 " ORDER BY lower(tagname) /*sort*/"
1261 );
 
 
 
 
 
 
 
1262 while( db_step(&q)==SQLITE_ROW ){
1263 const char *zName = db_column_text(&q, 0);
1264 fossil_print( "%s\n",zName);
1265 }
1266 db_finalize(&q);
1267
--- src/wiki.c
+++ src/wiki.c
@@ -1137,36 +1137,44 @@
1137 }
1138
1139 /*
1140 ** COMMAND: wiki*
1141 **
1142 ** Usage: ../fossil wiki (export|create|commit|list) WikiName
1143 **
1144 ** Run various subcommands to work with wiki entries or tech notes.
1145 **
1146 ** ../fossil wiki export ?PAGENAME? ?FILE? [-t|--technote DATETIME ]
1147 **
1148 ** Sends the latest version of either the PAGENAME wiki entry
1149 ** or the DATETIME tech note to the given file or standard
1150 ** output. One of PAGENAME or DATETIME must be specified.
1151 **
1152 ** ../fossil wiki (create|commit) PAGENAME ?FILE? ?OPTIONS?
1153 **
1154 ** Create a new or commit changes to an existing wiki page or
1155 ** technote from FILE or from standard input.
1156 **
1157 ** Options:
1158 ** -M|--mimetype TEXT-FORMAT The mime type of the update defaulting
1159 ** defaulting to the type used by the
1160 ** previous version of the page or (for
1161 ** new pages) text/x-fossil-wiki.
1162 ** -t|--technote DATETIME Specifies the timestamp of the technote
1163 ** to be created or updated.
1164 ** --technote-tags TAGS The set of tags for a technote.
1165 ** --technote-bgcolor COLOR The color used for the technote on the
1166 ** timeline.
1167 **
1168 ** ../fossil wiki list ?--technote?
1169 ** ../fossil wiki ls ?--technote?
1170 **
1171 ** Lists all wiki entries, one per line, ordered
1172 ** case-insensitively by name. The --technote flag
1173 ** specifies that technotes will be listed instead of
1174 ** the wiki entries, which will be listed in order
1175 ** timestamp.
1176 **
1177 */
1178 void wiki_cmd(void){
1179 int n;
1180 db_find_and_open_repository(0, 0);
@@ -1179,33 +1187,54 @@
1187 }
1188
1189 if( strncmp(g.argv[2],"export",n)==0 ){
1190 const char *zPageName; /* Name of the wiki page to export */
1191 const char *zFile; /* Name of the output file (0=stdout) */
1192 const char *zETime; /* The name of the technote to export */
1193 int rid; /* Artifact ID of the wiki page */
1194 int i; /* Loop counter */
1195 char *zBody = 0; /* Wiki page content */
1196 Blob body; /* Wiki page content */
1197 Manifest *pWiki = 0; /* Parsed wiki page content */
1198
1199 zETime = find_option("technote","t",1);
1200 if( !zETime ){
1201 if( (g.argc!=4) && (g.argc!=5) ){
1202 usage("export PAGENAME ?FILE?");
1203 }
1204 zPageName = g.argv[3];
1205 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1206 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1207 " ORDER BY x.mtime DESC LIMIT 1",
1208 zPageName
1209 );
1210 if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
1211 zBody = pWiki->zWiki;
1212 }
1213 if( zBody==0 ){
1214 fossil_fatal("wiki page [%s] not found",zPageName);
1215 }
1216 zFile = (g.argc==4) ? "-" : g.argv[4];
1217 }else{
1218 if( (g.argc!=3) && (g.argc!=4) ){
1219 usage("export ?FILE? --technote DATETIME");
1220 }
1221 rid = db_int(0, "SELECT objid FROM event"
1222 " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1223 " ORDER BY mtime DESC LIMIT 1",
1224 zETime
1225 );
1226 if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
1227 zBody = pWiki->zWiki;
1228 }
1229 if( zBody==0 ){
1230 fossil_fatal("technote not found");
1231 }
1232 zFile = (g.argc==3) ? "-" : g.argv[3];
1233 }
1234 for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
1235 zBody[i] = 0;
 
1236 blob_init(&body, zBody, -1);
1237 blob_append(&body, "\n", 1);
1238 blob_write_to_file(&body, zFile);
1239 blob_reset(&body);
1240 manifest_destroy(pWiki);
@@ -1215,37 +1244,70 @@
1244 const char *zPageName; /* page name */
1245 Blob content; /* Input content */
1246 int rid;
1247 Manifest *pWiki = 0; /* Parsed wiki page content */
1248 const char *zMimeType = find_option("mimetype", "M", 1);
1249 const char *zETime = find_option("technote", "t", 1);
1250 const char *zTags = find_option("technote-tags", NULL, 1);
1251 const char *zClr = find_option("technote-bgcolor", NULL, 1);
1252 if( g.argc!=4 && g.argc!=5 ){
1253 usage("commit|create PAGENAME ?FILE? [--mimetype TEXT-FORMAT]"
1254 " [--technote DATETIME] [--technote-tags TAGS]"
1255 " [--technote-bgcolor COLOR]");
1256 }
1257 zPageName = g.argv[3];
1258 if( g.argc==4 ){
1259 blob_read_from_channel(&content, stdin, -1);
1260 }else{
1261 blob_read_from_file(&content, g.argv[4]);
1262 }
1263 if(!zMimeType || !*zMimeType){
1264 /* Try to deduce the mime type based on the prior version. */
1265 if ( !zETime ){
1266 rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
1267 " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
1268 " ORDER BY x.mtime DESC LIMIT 1",
1269 zPageName
1270 );
1271 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0
1272 && (pWiki->zMimetype && *pWiki->zMimetype)){
1273 zMimeType = pWiki->zMimetype;
1274 }
1275 }else{
1276 rid = db_int(0, "SELECT objid FROM event"
1277 " WHERE datetime(mtime)=datetime('%q') AND type='e'"
1278 " ORDER BY mtime DESC LIMIT 1",
1279 zPageName
1280 );
1281 if(rid>0 && (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0
1282 && (pWiki->zMimetype && *pWiki->zMimetype)){
1283 zMimeType = pWiki->zMimetype;
1284 }
1285 }
1286 }
1287 if( !zETime ){
1288 if( g.argv[2][1]=='r' ){
1289 wiki_cmd_commit(zPageName, 1, &content, zMimeType, 1);
1290 fossil_print("Created new wiki page %s.\n", zPageName);
1291 }else{
1292 wiki_cmd_commit(zPageName, 0, &content, zMimeType, 1);
1293 fossil_print("Updated wiki page %s.\n", zPageName);
1294 }
1295 }else{
1296 char *zMETime; /* Normalized, mutable version of zETime */
1297 zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))",
1298 zETime);
1299 if( g.argv[2][1]=='r' ){
1300 event_cmd_commit(zMETime, 1, &content, zMimeType, zPageName,
1301 zTags, zClr);
1302 fossil_print("Created new tech note %s.\n", zMETime);
1303 }else{
1304 event_cmd_commit(zMETime, 0, &content, zMimeType, zPageName,
1305 zTags, zClr);
1306 fossil_print("Updated tech note %s.\n", zMETime);
1307 }
1308 free(zMETime);
1309 }
1310 manifest_destroy(pWiki);
1311 blob_reset(&content);
1312 }else if( strncmp(g.argv[2],"delete",n)==0 ){
1313 if( g.argc!=5 ){
@@ -1253,14 +1315,21 @@
1315 }
1316 fossil_fatal("delete not yet implemented.");
1317 }else if(( strncmp(g.argv[2],"list",n)==0 )
1318 || ( strncmp(g.argv[2],"ls",n)==0 )){
1319 Stmt q;
1320 if ( !find_option("technote","t",0) ){
1321 db_prepare(&q,
1322 "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
1323 " ORDER BY lower(tagname) /*sort*/"
1324 );
1325 }else{
1326 db_prepare(&q,
1327 "SELECT datetime(mtime) FROM event WHERE type='e'"
1328 " ORDER BY mtime /*sort*/"
1329 );
1330 }
1331 while( db_step(&q)==SQLITE_ROW ){
1332 const char *zName = db_column_text(&q, 0);
1333 fossil_print( "%s\n",zName);
1334 }
1335 db_finalize(&q);
1336

Keyboard Shortcuts

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