Fossil SCM
Add the ability to have attachments on technotes. Add command-line support for technotes in the "fossil wiki" command.
Commit
045deb27ce9f959fa8cc8ed20d9359eb2bb15223
Parent
5e81f4c02596f01…
7 files changed
+86
-22
+176
-82
+176
-82
+82
-3
+82
-3
+131
-62
+131
-62
+86
-22
| --- src/attach.c | ||
| +++ src/attach.c | ||
| @@ -26,28 +26,36 @@ | ||
| 26 | 26 | ** List attachments. |
| 27 | 27 | ** |
| 28 | 28 | ** tkt=TICKETUUID |
| 29 | 29 | ** page=WIKIPAGE |
| 30 | 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 | |
| 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. | |
| 35 | 37 | */ |
| 36 | 38 | void attachlist_page(void){ |
| 37 | 39 | const char *zPage = P("page"); |
| 38 | 40 | const char *zTkt = P("tkt"); |
| 41 | + const char *zTechNote = P("technote"); | |
| 39 | 42 | Blob sql; |
| 40 | 43 | Stmt q; |
| 41 | 44 | |
| 42 | 45 | if( zPage && zTkt ) zTkt = 0; |
| 43 | 46 | login_check_credentials(); |
| 44 | 47 | blob_zero(&sql); |
| 45 | 48 | blob_append_sql(&sql, |
| 46 | 49 | "SELECT datetime(mtime,toLocal()), src, target, filename," |
| 47 | 50 | " 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)" | |
| 49 | 57 | " FROM attachment" |
| 50 | 58 | ); |
| 51 | 59 | if( zPage ){ |
| 52 | 60 | if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } |
| 53 | 61 | style_header("Attachments To %h", zPage); |
| @@ -54,10 +62,15 @@ | ||
| 54 | 62 | blob_append_sql(&sql, " WHERE target=%Q", zPage); |
| 55 | 63 | }else if( zTkt ){ |
| 56 | 64 | if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } |
| 57 | 65 | style_header("Attachments To Ticket %S", zTkt); |
| 58 | 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); | |
| 59 | 72 | }else{ |
| 60 | 73 | if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){ |
| 61 | 74 | login_needed(g.anon.RdTkt || g.anon.RdWiki); |
| 62 | 75 | return; |
| 63 | 76 | } |
| @@ -73,21 +86,25 @@ | ||
| 73 | 86 | const char *zFilename = db_column_text(&q, 3); |
| 74 | 87 | const char *zComment = db_column_text(&q, 4); |
| 75 | 88 | const char *zUser = db_column_text(&q, 5); |
| 76 | 89 | const char *zUuid = db_column_text(&q, 6); |
| 77 | 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); | |
| 78 | 93 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 79 | 94 | int i; |
| 80 | 95 | char *zUrlTail; |
| 81 | 96 | for(i=0; zFilename[i]; i++){ |
| 82 | 97 | if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ |
| 83 | 98 | zFilename = &zFilename[i+1]; |
| 84 | 99 | i = -1; |
| 85 | 100 | } |
| 86 | 101 | } |
| 87 | - if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ | |
| 102 | + if( type==1 ){ | |
| 88 | 103 | zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); |
| 104 | + }else if( type==2 ){ | |
| 105 | + zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename); | |
| 89 | 106 | }else{ |
| 90 | 107 | zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); |
| 91 | 108 | } |
| 92 | 109 | @ <li><p> |
| 93 | 110 | @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a> |
| @@ -98,19 +115,22 @@ | ||
| 98 | 115 | @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br /> |
| 99 | 116 | if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; |
| 100 | 117 | if( zComment && zComment[0] ){ |
| 101 | 118 | @ %!W(zComment)<br /> |
| 102 | 119 | } |
| 103 | - if( zPage==0 && zTkt==0 ){ | |
| 120 | + if( zPage==0 && zTkt==0 && zTechNote==0 ){ | |
| 104 | 121 | if( zSrc==0 || zSrc[0]==0 ){ |
| 105 | 122 | zSrc = "Deleted from"; |
| 106 | 123 | }else { |
| 107 | 124 | zSrc = "Added to"; |
| 108 | 125 | } |
| 109 | - if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){ | |
| 126 | + if( type==1 ){ | |
| 110 | 127 | @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)"> |
| 111 | 128 | @ %S(zTarget)</a> |
| 129 | + }else if( type==2 ){ | |
| 130 | + @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)"> | |
| 131 | + @ %S(zTarget)</a> | |
| 112 | 132 | }else{ |
| 113 | 133 | @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)"> |
| 114 | 134 | @ %h(zTarget)</a> |
| 115 | 135 | } |
| 116 | 136 | }else{ |
| @@ -138,31 +158,35 @@ | ||
| 138 | 158 | ** Download or display an attachment. |
| 139 | 159 | ** Query parameters: |
| 140 | 160 | ** |
| 141 | 161 | ** tkt=TICKETUUID |
| 142 | 162 | ** page=WIKIPAGE |
| 163 | +** technote=TECHNOTEUUID | |
| 143 | 164 | ** file=FILENAME |
| 144 | 165 | ** attachid=ID |
| 145 | 166 | ** |
| 146 | 167 | */ |
| 147 | 168 | void attachview_page(void){ |
| 148 | 169 | const char *zPage = P("page"); |
| 149 | 170 | const char *zTkt = P("tkt"); |
| 171 | + const char *zTechNote = P("technote"); | |
| 150 | 172 | const char *zFile = P("file"); |
| 151 | 173 | const char *zTarget = 0; |
| 152 | 174 | int attachid = atoi(PD("attachid","0")); |
| 153 | 175 | char *zUUID; |
| 154 | 176 | |
| 155 | - if( zPage && zTkt ) zTkt = 0; | |
| 156 | 177 | if( zFile==0 ) fossil_redirect_home(); |
| 157 | 178 | login_check_credentials(); |
| 158 | 179 | if( zPage ){ |
| 159 | 180 | if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } |
| 160 | 181 | zTarget = zPage; |
| 161 | 182 | }else if( zTkt ){ |
| 162 | 183 | if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } |
| 163 | 184 | zTarget = zTkt; |
| 185 | + }else if( zTechNote ){ | |
| 186 | + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } | |
| 187 | + zTarget = zTechNote; | |
| 164 | 188 | }else{ |
| 165 | 189 | fossil_redirect_home(); |
| 166 | 190 | } |
| 167 | 191 | if( attachid>0 ){ |
| 168 | 192 | zUUID = db_text(0, |
| @@ -186,18 +210,19 @@ | ||
| 186 | 210 | }else if( zUUID[0]=='x' ){ |
| 187 | 211 | style_header("Missing"); |
| 188 | 212 | @ Attachment has been deleted |
| 189 | 213 | style_footer(); |
| 190 | 214 | 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 | 215 | }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 | + } | |
| 199 | 224 | } |
| 200 | 225 | } |
| 201 | 226 | |
| 202 | 227 | /* |
| 203 | 228 | ** Save an attachment control artifact into the repository |
| @@ -228,27 +253,34 @@ | ||
| 228 | 253 | ** WEBPAGE: attachadd |
| 229 | 254 | ** Add a new attachment. |
| 230 | 255 | ** |
| 231 | 256 | ** tkt=TICKETUUID |
| 232 | 257 | ** page=WIKIPAGE |
| 258 | +** technote=TECHNOTEUUID | |
| 233 | 259 | ** from=URL |
| 234 | 260 | ** |
| 235 | 261 | */ |
| 236 | 262 | void attachadd_page(void){ |
| 237 | 263 | const char *zPage = P("page"); |
| 238 | 264 | const char *zTkt = P("tkt"); |
| 265 | + const char *zTechNote = P("technote"); | |
| 239 | 266 | const char *zFrom = P("from"); |
| 240 | 267 | const char *aContent = P("f"); |
| 241 | 268 | const char *zName = PD("f:filename","unknown"); |
| 242 | 269 | const char *zTarget; |
| 243 | - const char *zTargetType; | |
| 270 | + char *zTargetType; | |
| 244 | 271 | int szContent = atoi(PD("f:bytes","0")); |
| 245 | 272 | int goodCaptcha = 1; |
| 246 | 273 | |
| 247 | 274 | 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(); | |
| 250 | 282 | login_check_credentials(); |
| 251 | 283 | if( zPage ){ |
| 252 | 284 | if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 253 | 285 | login_needed(g.anon.ApndWiki && g.anon.Attach); |
| 254 | 286 | return; |
| @@ -257,10 +289,24 @@ | ||
| 257 | 289 | fossil_redirect_home(); |
| 258 | 290 | } |
| 259 | 291 | zTarget = zPage; |
| 260 | 292 | zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>", |
| 261 | 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 | + | |
| 262 | 308 | }else{ |
| 263 | 309 | if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){ |
| 264 | 310 | login_needed(g.anon.ApndTkt && g.anon.Attach); |
| 265 | 311 | return; |
| 266 | 312 | } |
| @@ -340,10 +386,12 @@ | ||
| 340 | 386 | @ <input type="file" name="f" size="60" /><br /> |
| 341 | 387 | @ Description:<br /> |
| 342 | 388 | @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br /> |
| 343 | 389 | if( zTkt ){ |
| 344 | 390 | @ <input type="hidden" name="tkt" value="%h(zTkt)" /> |
| 391 | + }else if( zTechNote ){ | |
| 392 | + @ <input type="hidden" name="technote" value="%h(zTechNote)" /> | |
| 345 | 393 | }else{ |
| 346 | 394 | @ <input type="hidden" name="page" value="%h(zPage)" /> |
| 347 | 395 | } |
| 348 | 396 | @ <input type="hidden" name="from" value="%h(zFrom)" /> |
| 349 | 397 | @ <input type="submit" name="ok" value="Add Attachment" /> |
| @@ -350,10 +398,11 @@ | ||
| 350 | 398 | @ <input type="submit" name="cancel" value="Cancel" /> |
| 351 | 399 | @ </div> |
| 352 | 400 | captcha_generate(0); |
| 353 | 401 | @ </form> |
| 354 | 402 | style_footer(); |
| 403 | + fossil_free(zTargetType); | |
| 355 | 404 | } |
| 356 | 405 | |
| 357 | 406 | /* |
| 358 | 407 | ** WEBPAGE: ainfo |
| 359 | 408 | ** URL: /ainfo?name=ARTIFACTID |
| @@ -364,15 +413,16 @@ | ||
| 364 | 413 | int rid; /* RID for the control artifact */ |
| 365 | 414 | int ridSrc; /* RID for the attached file */ |
| 366 | 415 | char *zDate; /* Date attached */ |
| 367 | 416 | const char *zUuid; /* UUID of the control artifact */ |
| 368 | 417 | 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 */ | |
| 370 | 419 | const char *zSrc; /* UUID of the attached file */ |
| 371 | 420 | const char *zName; /* Name of the attached file */ |
| 372 | 421 | const char *zDesc; /* Description of the attached file */ |
| 373 | 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 */ | |
| 374 | 424 | const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */ |
| 375 | 425 | int modPending; /* True if awaiting moderation */ |
| 376 | 426 | const char *zModAction; /* Moderation action or NULL */ |
| 377 | 427 | int isModerator; /* TRUE if user is the moderator */ |
| 378 | 428 | const char *zMime; /* MIME Type */ |
| @@ -422,15 +472,23 @@ | ||
| 422 | 472 | zWikiName = zTarget; |
| 423 | 473 | if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
| 424 | 474 | if( g.perm.WrWiki ){ |
| 425 | 475 | style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid); |
| 426 | 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 | + } | |
| 427 | 483 | } |
| 428 | 484 | zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate); |
| 429 | 485 | |
| 430 | 486 | 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)) | |
| 432 | 490 | ){ |
| 433 | 491 | int i, n, rid; |
| 434 | 492 | char *zDate; |
| 435 | 493 | Blob manifest; |
| 436 | 494 | Blob cksum; |
| @@ -454,11 +512,13 @@ | ||
| 454 | 512 | db_end_transaction(0); |
| 455 | 513 | @ <p>The attachment below has been deleted.</p> |
| 456 | 514 | } |
| 457 | 515 | |
| 458 | 516 | 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)) | |
| 460 | 520 | ){ |
| 461 | 521 | form_begin(0, "%R/ainfo/%!S", zUuid); |
| 462 | 522 | @ <p>Confirm you want to delete the attachment shown below. |
| 463 | 523 | @ <input type="submit" name="confirm" value="Confirm"> |
| 464 | 524 | @ </form> |
| @@ -502,10 +562,14 @@ | ||
| 502 | 562 | } |
| 503 | 563 | if( zTktUuid ){ |
| 504 | 564 | @ <tr><th>Ticket:</th> |
| 505 | 565 | @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr> |
| 506 | 566 | } |
| 567 | + if( zTNUuid ){ | |
| 568 | + @ <tr><th>Tech Note:</th> | |
| 569 | + @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr> | |
| 570 | + } | |
| 507 | 571 | if( zWikiName ){ |
| 508 | 572 | @ <tr><th>Wiki Page:</th> |
| 509 | 573 | @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr> |
| 510 | 574 | } |
| 511 | 575 | @ <tr><th>Date:</th><td> |
| 512 | 576 |
| --- 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 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 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 @@ | ||
| 62 | 62 | ** Display an existing event identified by EVENTID |
| 63 | 63 | */ |
| 64 | 64 | void event_page(void){ |
| 65 | 65 | int rid = 0; /* rid of the event artifact */ |
| 66 | 66 | char *zUuid; /* UUID corresponding to rid */ |
| 67 | - const char *zId; /* Event identifier */ | |
| 67 | + const char *zId; /* Event identifier */ | |
| 68 | 68 | const char *zVerbose; /* Value of verbose option */ |
| 69 | 69 | char *zETime; /* Time of the tech-note */ |
| 70 | 70 | char *zATime; /* Time the artifact was created */ |
| 71 | 71 | int specRid; /* rid specified by aid= parameter */ |
| 72 | 72 | int prevRid, nextRid; /* Previous or next edits of this tech-note */ |
| @@ -75,10 +75,11 @@ | ||
| 75 | 75 | Blob title; /* Title extracted from the technote body */ |
| 76 | 76 | Blob tail; /* Event body that comes after the title */ |
| 77 | 77 | Stmt q1; /* Query to search for the technote */ |
| 78 | 78 | int verboseFlag; /* True to show details */ |
| 79 | 79 | const char *zMimetype = 0; /* Mimetype of the document */ |
| 80 | + const char *zFullId; /* Full event identifier */ | |
| 80 | 81 | |
| 81 | 82 | |
| 82 | 83 | /* wiki-read privilege is needed in order to read tech-notes. |
| 83 | 84 | */ |
| 84 | 85 | login_check_credentials(); |
| @@ -150,10 +151,15 @@ | ||
| 150 | 151 | tail = fullbody; |
| 151 | 152 | } |
| 152 | 153 | style_header("%s", blob_str(&title)); |
| 153 | 154 | if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){ |
| 154 | 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 | + } | |
| 155 | 161 | } |
| 156 | 162 | zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate); |
| 157 | 163 | style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId); |
| 158 | 164 | if( g.perm.Hyperlink ){ |
| 159 | 165 | if( verboseFlag ){ |
| @@ -216,13 +222,123 @@ | ||
| 216 | 222 | }else{ |
| 217 | 223 | @ <pre> |
| 218 | 224 | @ %h(blob_str(&fullbody)) |
| 219 | 225 | @ </pre> |
| 220 | 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>"); | |
| 221 | 232 | style_footer(); |
| 222 | 233 | manifest_destroy(pTNote); |
| 223 | 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 | +} | |
| 224 | 340 | |
| 225 | 341 | /* |
| 226 | 342 | ** WEBPAGE: technoteedit |
| 227 | 343 | ** WEBPAGE: eventedit |
| 228 | 344 | ** |
| @@ -323,97 +439,19 @@ | ||
| 323 | 439 | ); |
| 324 | 440 | } |
| 325 | 441 | } |
| 326 | 442 | zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); |
| 327 | 443 | 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 | 444 | 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) ){ | |
| 406 | 447 | style_header("Error"); |
| 407 | 448 | @ Internal error: Fossil tried to make an invalid artifact for |
| 408 | - @ the edited technode. | |
| 449 | + @ the edited technote. | |
| 409 | 450 | style_footer(); |
| 410 | 451 | return; |
| 411 | 452 | } |
| 412 | - assert( blob_is_reset(&event) ); | |
| 413 | - content_deltify(rid, nrid, 0); | |
| 414 | - db_end_transaction(0); | |
| 415 | 453 | cgi_redirectf("technote?name=%T", zId); |
| 416 | 454 | } |
| 417 | 455 | if( P("cancel")!=0 ){ |
| 418 | 456 | cgi_redirectf("technote?name=%T", zId); |
| 419 | 457 | return; |
| @@ -497,5 +535,61 @@ | ||
| 497 | 535 | @ <input type="submit" name="cancel" value="Cancel" /> |
| 498 | 536 | @ </td></tr></table> |
| 499 | 537 | @ </div></form> |
| 500 | 538 | style_footer(); |
| 501 | 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 | +} | |
| 502 | 596 |
| --- 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 @@ | ||
| 62 | 62 | ** Display an existing event identified by EVENTID |
| 63 | 63 | */ |
| 64 | 64 | void event_page(void){ |
| 65 | 65 | int rid = 0; /* rid of the event artifact */ |
| 66 | 66 | char *zUuid; /* UUID corresponding to rid */ |
| 67 | - const char *zId; /* Event identifier */ | |
| 67 | + const char *zId; /* Event identifier */ | |
| 68 | 68 | const char *zVerbose; /* Value of verbose option */ |
| 69 | 69 | char *zETime; /* Time of the tech-note */ |
| 70 | 70 | char *zATime; /* Time the artifact was created */ |
| 71 | 71 | int specRid; /* rid specified by aid= parameter */ |
| 72 | 72 | int prevRid, nextRid; /* Previous or next edits of this tech-note */ |
| @@ -75,10 +75,11 @@ | ||
| 75 | 75 | Blob title; /* Title extracted from the technote body */ |
| 76 | 76 | Blob tail; /* Event body that comes after the title */ |
| 77 | 77 | Stmt q1; /* Query to search for the technote */ |
| 78 | 78 | int verboseFlag; /* True to show details */ |
| 79 | 79 | const char *zMimetype = 0; /* Mimetype of the document */ |
| 80 | + const char *zFullId; /* Full event identifier */ | |
| 80 | 81 | |
| 81 | 82 | |
| 82 | 83 | /* wiki-read privilege is needed in order to read tech-notes. |
| 83 | 84 | */ |
| 84 | 85 | login_check_credentials(); |
| @@ -150,10 +151,15 @@ | ||
| 150 | 151 | tail = fullbody; |
| 151 | 152 | } |
| 152 | 153 | style_header("%s", blob_str(&title)); |
| 153 | 154 | if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){ |
| 154 | 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 | + } | |
| 155 | 161 | } |
| 156 | 162 | zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate); |
| 157 | 163 | style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId); |
| 158 | 164 | if( g.perm.Hyperlink ){ |
| 159 | 165 | if( verboseFlag ){ |
| @@ -216,13 +222,123 @@ | ||
| 216 | 222 | }else{ |
| 217 | 223 | @ <pre> |
| 218 | 224 | @ %h(blob_str(&fullbody)) |
| 219 | 225 | @ </pre> |
| 220 | 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>"); | |
| 221 | 232 | style_footer(); |
| 222 | 233 | manifest_destroy(pTNote); |
| 223 | 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 | +} | |
| 224 | 340 | |
| 225 | 341 | /* |
| 226 | 342 | ** WEBPAGE: technoteedit |
| 227 | 343 | ** WEBPAGE: eventedit |
| 228 | 344 | ** |
| @@ -323,97 +439,19 @@ | ||
| 323 | 439 | ); |
| 324 | 440 | } |
| 325 | 441 | } |
| 326 | 442 | zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); |
| 327 | 443 | 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 | 444 | 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) ){ | |
| 406 | 447 | style_header("Error"); |
| 407 | 448 | @ Internal error: Fossil tried to make an invalid artifact for |
| 408 | - @ the edited technode. | |
| 449 | + @ the edited technote. | |
| 409 | 450 | style_footer(); |
| 410 | 451 | return; |
| 411 | 452 | } |
| 412 | - assert( blob_is_reset(&event) ); | |
| 413 | - content_deltify(rid, nrid, 0); | |
| 414 | - db_end_transaction(0); | |
| 415 | 453 | cgi_redirectf("technote?name=%T", zId); |
| 416 | 454 | } |
| 417 | 455 | if( P("cancel")!=0 ){ |
| 418 | 456 | cgi_redirectf("technote?name=%T", zId); |
| 419 | 457 | return; |
| @@ -497,5 +535,61 @@ | ||
| 497 | 535 | @ <input type="submit" name="cancel" value="Cancel" /> |
| 498 | 536 | @ </td></tr></table> |
| 499 | 537 | @ </div></form> |
| 500 | 538 | style_footer(); |
| 501 | 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 | +} | |
| 502 | 596 |
| --- 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 @@ | ||
| 2008 | 2008 | char *zTag = mprintf("event-%s", p->zEventId); |
| 2009 | 2009 | int tagid = tag_findid(zTag, 1); |
| 2010 | 2010 | int prior, subsequent; |
| 2011 | 2011 | int nWiki; |
| 2012 | 2012 | char zLength[40]; |
| 2013 | + Stmt qatt; | |
| 2013 | 2014 | while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; |
| 2014 | 2015 | nWiki = strlen(p->zWiki); |
| 2015 | 2016 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 2016 | 2017 | tag_insert(zTag, 1, zLength, rid, p->rDate, rid); |
| 2017 | 2018 | fossil_free(zTag); |
| @@ -2049,26 +2050,95 @@ | ||
| 2049 | 2050 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 2050 | 2051 | p->rEventDate, rid, tagid, p->zUser, p->zComment, |
| 2051 | 2052 | TAG_BGCOLOR, rid |
| 2052 | 2053 | ); |
| 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); | |
| 2054 | 2083 | } |
| 2055 | 2084 | if( p->type==CFTYPE_TICKET ){ |
| 2056 | 2085 | char *zTag; |
| 2086 | + Stmt qatt; | |
| 2057 | 2087 | assert( manifest_crosslink_busy==1 ); |
| 2058 | 2088 | zTag = mprintf("tkt-%s", p->zTicketUuid); |
| 2059 | 2089 | tag_insert(zTag, 1, 0, rid, p->rDate, rid); |
| 2060 | 2090 | fossil_free(zTag); |
| 2061 | 2091 | db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", |
| 2062 | 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); | |
| 2063 | 2120 | } |
| 2064 | 2121 | if( p->type==CFTYPE_ATTACHMENT ){ |
| 2065 | 2122 | char *zComment = 0; |
| 2066 | 2123 | 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 | + } | |
| 2070 | 2140 | db_multi_exec( |
| 2071 | 2141 | "INSERT INTO attachment(attachid, mtime, src, target," |
| 2072 | 2142 | "filename, comment, user)" |
| 2073 | 2143 | "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", |
| 2074 | 2144 | rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName, |
| @@ -2089,10 +2159,19 @@ | ||
| 2089 | 2159 | p->zAttachSrc, p->zAttachName, p->zAttachTarget); |
| 2090 | 2160 | }else{ |
| 2091 | 2161 | zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", |
| 2092 | 2162 | p->zAttachName, p->zAttachTarget); |
| 2093 | 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 | + } | |
| 2094 | 2173 | }else{ |
| 2095 | 2174 | if( isAdd ){ |
| 2096 | 2175 | zComment = mprintf( |
| 2097 | 2176 | "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]", |
| 2098 | 2177 | p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget); |
| 2099 | 2178 |
| --- 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 @@ | ||
| 2008 | 2008 | char *zTag = mprintf("event-%s", p->zEventId); |
| 2009 | 2009 | int tagid = tag_findid(zTag, 1); |
| 2010 | 2010 | int prior, subsequent; |
| 2011 | 2011 | int nWiki; |
| 2012 | 2012 | char zLength[40]; |
| 2013 | + Stmt qatt; | |
| 2013 | 2014 | while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; |
| 2014 | 2015 | nWiki = strlen(p->zWiki); |
| 2015 | 2016 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 2016 | 2017 | tag_insert(zTag, 1, zLength, rid, p->rDate, rid); |
| 2017 | 2018 | fossil_free(zTag); |
| @@ -2049,26 +2050,95 @@ | ||
| 2049 | 2050 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 2050 | 2051 | p->rEventDate, rid, tagid, p->zUser, p->zComment, |
| 2051 | 2052 | TAG_BGCOLOR, rid |
| 2052 | 2053 | ); |
| 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); | |
| 2054 | 2083 | } |
| 2055 | 2084 | if( p->type==CFTYPE_TICKET ){ |
| 2056 | 2085 | char *zTag; |
| 2086 | + Stmt qatt; | |
| 2057 | 2087 | assert( manifest_crosslink_busy==1 ); |
| 2058 | 2088 | zTag = mprintf("tkt-%s", p->zTicketUuid); |
| 2059 | 2089 | tag_insert(zTag, 1, 0, rid, p->rDate, rid); |
| 2060 | 2090 | fossil_free(zTag); |
| 2061 | 2091 | db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", |
| 2062 | 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); | |
| 2063 | 2120 | } |
| 2064 | 2121 | if( p->type==CFTYPE_ATTACHMENT ){ |
| 2065 | 2122 | char *zComment = 0; |
| 2066 | 2123 | 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 | + } | |
| 2070 | 2140 | db_multi_exec( |
| 2071 | 2141 | "INSERT INTO attachment(attachid, mtime, src, target," |
| 2072 | 2142 | "filename, comment, user)" |
| 2073 | 2143 | "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", |
| 2074 | 2144 | rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName, |
| @@ -2089,10 +2159,19 @@ | ||
| 2089 | 2159 | p->zAttachSrc, p->zAttachName, p->zAttachTarget); |
| 2090 | 2160 | }else{ |
| 2091 | 2161 | zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", |
| 2092 | 2162 | p->zAttachName, p->zAttachTarget); |
| 2093 | 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 | + } | |
| 2094 | 2173 | }else{ |
| 2095 | 2174 | if( isAdd ){ |
| 2096 | 2175 | zComment = mprintf( |
| 2097 | 2176 | "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]", |
| 2098 | 2177 | p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget); |
| 2099 | 2178 |
| --- 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 @@ | ||
| 1137 | 1137 | } |
| 1138 | 1138 | |
| 1139 | 1139 | /* |
| 1140 | 1140 | ** COMMAND: wiki* |
| 1141 | 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. | |
| 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. | |
| 1168 | 1176 | ** |
| 1169 | 1177 | */ |
| 1170 | 1178 | void wiki_cmd(void){ |
| 1171 | 1179 | int n; |
| 1172 | 1180 | db_find_and_open_repository(0, 0); |
| @@ -1179,33 +1187,54 @@ | ||
| 1179 | 1187 | } |
| 1180 | 1188 | |
| 1181 | 1189 | if( strncmp(g.argv[2],"export",n)==0 ){ |
| 1182 | 1190 | const char *zPageName; /* Name of the wiki page to export */ |
| 1183 | 1191 | const char *zFile; /* Name of the output file (0=stdout) */ |
| 1192 | + const char *zETime; /* The name of the technote to export */ | |
| 1184 | 1193 | int rid; /* Artifact ID of the wiki page */ |
| 1185 | 1194 | int i; /* Loop counter */ |
| 1186 | 1195 | char *zBody = 0; /* Wiki page content */ |
| 1187 | 1196 | Blob body; /* Wiki page content */ |
| 1188 | 1197 | 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]; | |
| 1203 | 1233 | } |
| 1204 | 1234 | for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){} |
| 1205 | 1235 | zBody[i] = 0; |
| 1206 | - zFile = (g.argc==4) ? "-" : g.argv[4]; | |
| 1207 | 1236 | blob_init(&body, zBody, -1); |
| 1208 | 1237 | blob_append(&body, "\n", 1); |
| 1209 | 1238 | blob_write_to_file(&body, zFile); |
| 1210 | 1239 | blob_reset(&body); |
| 1211 | 1240 | manifest_destroy(pWiki); |
| @@ -1215,37 +1244,70 @@ | ||
| 1215 | 1244 | const char *zPageName; /* page name */ |
| 1216 | 1245 | Blob content; /* Input content */ |
| 1217 | 1246 | int rid; |
| 1218 | 1247 | Manifest *pWiki = 0; /* Parsed wiki page content */ |
| 1219 | 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); | |
| 1220 | 1252 | 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]"); | |
| 1222 | 1256 | } |
| 1223 | 1257 | zPageName = g.argv[3]; |
| 1224 | 1258 | if( g.argc==4 ){ |
| 1225 | 1259 | blob_read_from_channel(&content, stdin, -1); |
| 1226 | 1260 | }else{ |
| 1227 | 1261 | blob_read_from_file(&content, g.argv[4]); |
| 1228 | 1262 | } |
| 1229 | 1263 | if(!zMimeType || !*zMimeType){ |
| 1230 | 1264 | /* 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); | |
| 1247 | 1309 | } |
| 1248 | 1310 | manifest_destroy(pWiki); |
| 1249 | 1311 | blob_reset(&content); |
| 1250 | 1312 | }else if( strncmp(g.argv[2],"delete",n)==0 ){ |
| 1251 | 1313 | if( g.argc!=5 ){ |
| @@ -1253,14 +1315,21 @@ | ||
| 1253 | 1315 | } |
| 1254 | 1316 | fossil_fatal("delete not yet implemented."); |
| 1255 | 1317 | }else if(( strncmp(g.argv[2],"list",n)==0 ) |
| 1256 | 1318 | || ( strncmp(g.argv[2],"ls",n)==0 )){ |
| 1257 | 1319 | 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 | + } | |
| 1262 | 1331 | while( db_step(&q)==SQLITE_ROW ){ |
| 1263 | 1332 | const char *zName = db_column_text(&q, 0); |
| 1264 | 1333 | fossil_print( "%s\n",zName); |
| 1265 | 1334 | } |
| 1266 | 1335 | db_finalize(&q); |
| 1267 | 1336 |
| --- 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 @@ | ||
| 1137 | 1137 | } |
| 1138 | 1138 | |
| 1139 | 1139 | /* |
| 1140 | 1140 | ** COMMAND: wiki* |
| 1141 | 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. | |
| 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. | |
| 1168 | 1176 | ** |
| 1169 | 1177 | */ |
| 1170 | 1178 | void wiki_cmd(void){ |
| 1171 | 1179 | int n; |
| 1172 | 1180 | db_find_and_open_repository(0, 0); |
| @@ -1179,33 +1187,54 @@ | ||
| 1179 | 1187 | } |
| 1180 | 1188 | |
| 1181 | 1189 | if( strncmp(g.argv[2],"export",n)==0 ){ |
| 1182 | 1190 | const char *zPageName; /* Name of the wiki page to export */ |
| 1183 | 1191 | const char *zFile; /* Name of the output file (0=stdout) */ |
| 1192 | + const char *zETime; /* The name of the technote to export */ | |
| 1184 | 1193 | int rid; /* Artifact ID of the wiki page */ |
| 1185 | 1194 | int i; /* Loop counter */ |
| 1186 | 1195 | char *zBody = 0; /* Wiki page content */ |
| 1187 | 1196 | Blob body; /* Wiki page content */ |
| 1188 | 1197 | 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]; | |
| 1203 | 1233 | } |
| 1204 | 1234 | for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){} |
| 1205 | 1235 | zBody[i] = 0; |
| 1206 | - zFile = (g.argc==4) ? "-" : g.argv[4]; | |
| 1207 | 1236 | blob_init(&body, zBody, -1); |
| 1208 | 1237 | blob_append(&body, "\n", 1); |
| 1209 | 1238 | blob_write_to_file(&body, zFile); |
| 1210 | 1239 | blob_reset(&body); |
| 1211 | 1240 | manifest_destroy(pWiki); |
| @@ -1215,37 +1244,70 @@ | ||
| 1215 | 1244 | const char *zPageName; /* page name */ |
| 1216 | 1245 | Blob content; /* Input content */ |
| 1217 | 1246 | int rid; |
| 1218 | 1247 | Manifest *pWiki = 0; /* Parsed wiki page content */ |
| 1219 | 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); | |
| 1220 | 1252 | 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]"); | |
| 1222 | 1256 | } |
| 1223 | 1257 | zPageName = g.argv[3]; |
| 1224 | 1258 | if( g.argc==4 ){ |
| 1225 | 1259 | blob_read_from_channel(&content, stdin, -1); |
| 1226 | 1260 | }else{ |
| 1227 | 1261 | blob_read_from_file(&content, g.argv[4]); |
| 1228 | 1262 | } |
| 1229 | 1263 | if(!zMimeType || !*zMimeType){ |
| 1230 | 1264 | /* 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); | |
| 1247 | 1309 | } |
| 1248 | 1310 | manifest_destroy(pWiki); |
| 1249 | 1311 | blob_reset(&content); |
| 1250 | 1312 | }else if( strncmp(g.argv[2],"delete",n)==0 ){ |
| 1251 | 1313 | if( g.argc!=5 ){ |
| @@ -1253,14 +1315,21 @@ | ||
| 1253 | 1315 | } |
| 1254 | 1316 | fossil_fatal("delete not yet implemented."); |
| 1255 | 1317 | }else if(( strncmp(g.argv[2],"list",n)==0 ) |
| 1256 | 1318 | || ( strncmp(g.argv[2],"ls",n)==0 )){ |
| 1257 | 1319 | 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 | + } | |
| 1262 | 1331 | while( db_step(&q)==SQLITE_ROW ){ |
| 1263 | 1332 | const char *zName = db_column_text(&q, 0); |
| 1264 | 1333 | fossil_print( "%s\n",zName); |
| 1265 | 1334 | } |
| 1266 | 1335 | db_finalize(&q); |
| 1267 | 1336 |
| --- 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 |