| | @@ -118,10 +118,48 @@ |
| 118 | 118 | */ |
| 119 | 119 | static int is_sandbox(const char *zPagename){ |
| 120 | 120 | return fossil_stricmp(zPagename,"sandbox")==0 || |
| 121 | 121 | fossil_stricmp(zPagename,"sand box")==0; |
| 122 | 122 | } |
| 123 | + |
| 124 | +/* |
| 125 | +** Only allow certain mimetypes through. |
| 126 | +** All others become "text/x-fossil-wiki" |
| 127 | +*/ |
| 128 | +const char *wiki_filter_mimetypes(const char *zMimetype){ |
| 129 | + if( zMimetype!=0 && |
| 130 | + ( fossil_strcmp(zMimetype, "text/x-markdown")==0 |
| 131 | + || fossil_strcmp(zMimetype, "text/plain")==0 ) |
| 132 | + ){ |
| 133 | + return zMimetype; |
| 134 | + } |
| 135 | + return "text/x-fossil-wiki"; |
| 136 | +} |
| 137 | + |
| 138 | +/* |
| 139 | +** Render wiki text according to its mimetype |
| 140 | +*/ |
| 141 | +void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){ |
| 142 | + if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
| 143 | + wiki_convert(pWiki, 0, 0); |
| 144 | + }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
| 145 | + Blob title = BLOB_INITIALIZER; |
| 146 | + Blob tail = BLOB_INITIALIZER; |
| 147 | + markdown_to_html(pWiki, &title, &tail); |
| 148 | + if( blob_size(&title)>0 ){ |
| 149 | + @ <h1>%s(blob_str(&title))</h1> |
| 150 | + } |
| 151 | + @ %s(blob_str(&tail)) |
| 152 | + blob_reset(&title); |
| 153 | + blob_reset(&tail); |
| 154 | + }else{ |
| 155 | + @ <pre> |
| 156 | + @ %h(blob_str(pWiki)) |
| 157 | + @ </pre> |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 123 | 161 | |
| 124 | 162 | /* |
| 125 | 163 | ** WEBPAGE: wiki |
| 126 | 164 | ** URL: /wiki?name=PAGENAME |
| 127 | 165 | */ |
| | @@ -131,10 +169,11 @@ |
| 131 | 169 | int isSandbox; |
| 132 | 170 | char *zUuid; |
| 133 | 171 | Blob wiki; |
| 134 | 172 | Manifest *pWiki = 0; |
| 135 | 173 | const char *zPageName; |
| 174 | + const char *zMimetype; |
| 136 | 175 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 137 | 176 | |
| 138 | 177 | login_check_credentials(); |
| 139 | 178 | if( !g.perm.RdWiki ){ login_needed(); return; } |
| 140 | 179 | zPageName = P("name"); |
| | @@ -173,10 +212,11 @@ |
| 173 | 212 | } |
| 174 | 213 | if( check_name(zPageName) ) return; |
| 175 | 214 | isSandbox = is_sandbox(zPageName); |
| 176 | 215 | if( isSandbox ){ |
| 177 | 216 | zBody = db_get("sandbox",zBody); |
| 217 | + zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| 178 | 218 | rid = 0; |
| 179 | 219 | }else{ |
| 180 | 220 | zTag = mprintf("wiki-%s", zPageName); |
| 181 | 221 | rid = db_int(0, |
| 182 | 222 | "SELECT rid FROM tagxref" |
| | @@ -185,12 +225,14 @@ |
| 185 | 225 | ); |
| 186 | 226 | free(zTag); |
| 187 | 227 | pWiki = manifest_get(rid, CFTYPE_WIKI); |
| 188 | 228 | if( pWiki ){ |
| 189 | 229 | zBody = pWiki->zWiki; |
| 230 | + zMimetype = pWiki->zMimetype; |
| 190 | 231 | } |
| 191 | 232 | } |
| 233 | + zMimetype = wiki_filter_mimetypes(zMimetype); |
| 192 | 234 | if( !g.isHome ){ |
| 193 | 235 | if( rid ){ |
| 194 | 236 | style_submenu_element("Diff", "Last change", |
| 195 | 237 | "%R/wdiff?name=%T&a=%d", zPageName, rid); |
| 196 | 238 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| | @@ -212,22 +254,23 @@ |
| 212 | 254 | style_submenu_element("Attach", "Add An Attachment", |
| 213 | 255 | "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", |
| 214 | 256 | g.zTop, zPageName, g.zTop, zPageName); |
| 215 | 257 | } |
| 216 | 258 | if( rid && g.perm.ApndWiki ){ |
| 217 | | - style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T", |
| 218 | | - g.zTop, zPageName); |
| 259 | + style_submenu_element("Append", "Add A Comment", |
| 260 | + "%s/wikiappend?name=%T&mimetype=%s", |
| 261 | + g.zTop, zPageName, zMimetype); |
| 219 | 262 | } |
| 220 | 263 | if( g.perm.Hyperlink ){ |
| 221 | 264 | style_submenu_element("History", "History", "%s/whistory?name=%T", |
| 222 | 265 | g.zTop, zPageName); |
| 223 | 266 | } |
| 224 | 267 | } |
| 225 | 268 | style_set_current_page("%s?name=%T", g.zPath, zPageName); |
| 226 | 269 | style_header(zPageName); |
| 227 | 270 | blob_init(&wiki, zBody, -1); |
| 228 | | - wiki_convert(&wiki, 0, 0); |
| 271 | + wiki_render_by_mimetype(&wiki, zMimetype); |
| 229 | 272 | blob_reset(&wiki); |
| 230 | 273 | attachment_list(zPageName, "<hr /><h2>Attachments:</h2><ul>"); |
| 231 | 274 | manifest_destroy(pWiki); |
| 232 | 275 | style_footer(); |
| 233 | 276 | } |
| | @@ -247,10 +290,49 @@ |
| 247 | 290 | } |
| 248 | 291 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); |
| 249 | 292 | db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid); |
| 250 | 293 | manifest_crosslink(nrid, pWiki); |
| 251 | 294 | } |
| 295 | + |
| 296 | +/* |
| 297 | +** Formal names and common names for the various wiki styles. |
| 298 | +*/ |
| 299 | +static const char *azStyles[] = { |
| 300 | + "text/x-fossil-wiki", "Fossil Wiki", |
| 301 | + "text/x-markdown", "Markdown", |
| 302 | + "text/plain", "Plain Text" |
| 303 | +}; |
| 304 | + |
| 305 | +/* |
| 306 | +** Output a selection box from which the user can select the |
| 307 | +** wiki mimetype. |
| 308 | +*/ |
| 309 | +static void mimetype_option_menu(const char *zMimetype){ |
| 310 | + unsigned i; |
| 311 | + @ Markup style: <select name="mimetype" size="1"> |
| 312 | + for(i=0; i<sizeof(azStyles)/sizeof(azStyles[0]); i+=2){ |
| 313 | + if( fossil_strcmp(zMimetype,azStyles[i])==0 ){ |
| 314 | + @ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option> |
| 315 | + }else{ |
| 316 | + @ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option> |
| 317 | + } |
| 318 | + } |
| 319 | + @ </select> |
| 320 | +} |
| 321 | + |
| 322 | +/* |
| 323 | +** Given a mimetype, return its common name. |
| 324 | +*/ |
| 325 | +static const char *mimetype_common_name(const char *zMimetype){ |
| 326 | + int i; |
| 327 | + for(i=4; i>=2; i-=2){ |
| 328 | + if( zMimetype && fossil_strcmp(zMimetype, azStyles[i])==0 ){ |
| 329 | + return azStyles[i+1]; |
| 330 | + } |
| 331 | + } |
| 332 | + return azStyles[1]; |
| 333 | +} |
| 252 | 334 | |
| 253 | 335 | /* |
| 254 | 336 | ** WEBPAGE: wikiedit |
| 255 | 337 | ** URL: /wikiedit?name=PAGENAME |
| 256 | 338 | */ |
| | @@ -262,10 +344,11 @@ |
| 262 | 344 | Manifest *pWiki = 0; |
| 263 | 345 | const char *zPageName; |
| 264 | 346 | int n; |
| 265 | 347 | const char *z; |
| 266 | 348 | char *zBody = (char*)P("w"); |
| 349 | + const char *zMimetype = wiki_filter_mimetypes(P("mimetype")); |
| 267 | 350 | int isWysiwyg = P("wysiwyg")!=0; |
| 268 | 351 | int goodCaptcha = 1; |
| 269 | 352 | |
| 270 | 353 | if( P("edit-wysiwyg")!=0 ){ isWysiwyg = 1; zBody = 0; } |
| 271 | 354 | if( P("edit-markup")!=0 ){ isWysiwyg = 0; zBody = 0; } |
| | @@ -288,10 +371,11 @@ |
| 288 | 371 | login_needed(); |
| 289 | 372 | return; |
| 290 | 373 | } |
| 291 | 374 | if( zBody==0 ){ |
| 292 | 375 | zBody = db_get("sandbox",""); |
| 376 | + zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| 293 | 377 | } |
| 294 | 378 | }else{ |
| 295 | 379 | zTag = mprintf("wiki-%s", zPageName); |
| 296 | 380 | rid = db_int(0, |
| 297 | 381 | "SELECT rid FROM tagxref" |
| | @@ -303,10 +387,11 @@ |
| 303 | 387 | login_needed(); |
| 304 | 388 | return; |
| 305 | 389 | } |
| 306 | 390 | if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ |
| 307 | 391 | zBody = pWiki->zWiki; |
| 392 | + zMimetype = pWiki->zMimetype; |
| 308 | 393 | } |
| 309 | 394 | } |
| 310 | 395 | if( P("submit")!=0 && zBody!=0 |
| 311 | 396 | && (goodCaptcha = captcha_is_correct()) |
| 312 | 397 | ){ |
| | @@ -314,16 +399,20 @@ |
| 314 | 399 | Blob cksum; |
| 315 | 400 | blob_zero(&wiki); |
| 316 | 401 | db_begin_transaction(); |
| 317 | 402 | if( isSandbox ){ |
| 318 | 403 | db_set("sandbox",zBody,0); |
| 404 | + db_set("sandbox-mimetype",zMimetype,0); |
| 319 | 405 | }else{ |
| 320 | 406 | login_verify_csrf_secret(); |
| 321 | 407 | zDate = date_in_standard_format("now"); |
| 322 | 408 | blob_appendf(&wiki, "D %s\n", zDate); |
| 323 | 409 | free(zDate); |
| 324 | 410 | blob_appendf(&wiki, "L %F\n", zPageName); |
| 411 | + if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")!=0 ){ |
| 412 | + blob_appendf(&wiki, "N %s\n", zMimetype); |
| 413 | + } |
| 325 | 414 | if( rid ){ |
| 326 | 415 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 327 | 416 | blob_appendf(&wiki, "P %s\n", zUuid); |
| 328 | 417 | free(zUuid); |
| 329 | 418 | } |
| | @@ -353,11 +442,11 @@ |
| 353 | 442 | } |
| 354 | 443 | blob_zero(&wiki); |
| 355 | 444 | blob_append(&wiki, zBody, -1); |
| 356 | 445 | if( P("preview")!=0 ){ |
| 357 | 446 | @ Preview:<hr /> |
| 358 | | - wiki_convert(&wiki, 0, 0); |
| 447 | + wiki_render_by_mimetype(&wiki, zMimetype); |
| 359 | 448 | @ <hr /> |
| 360 | 449 | blob_reset(&wiki); |
| 361 | 450 | } |
| 362 | 451 | for(n=2, z=zBody; z[0]; z++){ |
| 363 | 452 | if( z[0]=='\n' ) n++; |
| | @@ -366,11 +455,12 @@ |
| 366 | 455 | if( n>30 ) n = 30; |
| 367 | 456 | if( !isWysiwyg ){ |
| 368 | 457 | /* Traditional markup-only editing */ |
| 369 | 458 | form_begin(0, "%R/wikiedit"); |
| 370 | 459 | @ <div> |
| 371 | | - @ <textarea name="w" class="wikiedit" cols="80" |
| 460 | + mimetype_option_menu(zMimetype); |
| 461 | + @ <br /><textarea name="w" class="wikiedit" cols="80" |
| 372 | 462 | @ rows="%d(n)" wrap="virtual">%h(zBody)</textarea> |
| 373 | 463 | @ <br /> |
| 374 | 464 | if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| 375 | 465 | @ <input type="submit" name="edit-wysiwyg" value="Wysiwyg Editor" |
| 376 | 466 | @ onclick='return confirm("Switching to WYSIWYG-mode\nwill erase your markup\nedits. Continue?")' /> |
| | @@ -413,30 +503,35 @@ |
| 413 | 503 | ** Prompt the user to enter the name of a new wiki page. Then redirect |
| 414 | 504 | ** to the wikiedit screen for that new page. |
| 415 | 505 | */ |
| 416 | 506 | void wikinew_page(void){ |
| 417 | 507 | const char *zName; |
| 508 | + const char *zMimetype; |
| 418 | 509 | login_check_credentials(); |
| 419 | 510 | if( !g.perm.NewWiki ){ |
| 420 | 511 | login_needed(); |
| 421 | 512 | return; |
| 422 | 513 | } |
| 423 | 514 | zName = PD("name",""); |
| 515 | + zMimetype = wiki_filter_mimetypes(P("mimetype")); |
| 424 | 516 | if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){ |
| 425 | | - if( db_get_boolean("wysiwyg-wiki", 0) ){ |
| 517 | + if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 |
| 518 | + && db_get_boolean("wysiwyg-wiki", 0) |
| 519 | + ){ |
| 426 | 520 | cgi_redirectf("wikiedit?name=%T&wysiwyg=1", zName); |
| 427 | 521 | }else{ |
| 428 | | - cgi_redirectf("wikiedit?name=%T", zName); |
| 522 | + cgi_redirectf("wikiedit?name=%T&mimetype=%s", zName, zMimetype); |
| 429 | 523 | } |
| 430 | 524 | } |
| 431 | 525 | style_header("Create A New Wiki Page"); |
| 432 | 526 | @ <p>Rules for wiki page names:</p> |
| 433 | 527 | well_formed_wiki_name_rules(); |
| 434 | 528 | form_begin(0, "%R/wikinew"); |
| 435 | 529 | @ <p>Name of new wiki page: |
| 436 | | - @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /> |
| 437 | | - @ <input type="submit" value="Create" /> |
| 530 | + @ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br /> |
| 531 | + mimetype_option_menu("text/x-fossil-wiki"); |
| 532 | + @ <br /><input type="submit" value="Create" /> |
| 438 | 533 | @ </p></form> |
| 439 | 534 | if( zName[0] ){ |
| 440 | 535 | @ <p><span class="wikiError"> |
| 441 | 536 | @ "%h(zName)" is not a valid wiki page name!</span></p> |
| 442 | 537 | } |
| | @@ -445,43 +540,61 @@ |
| 445 | 540 | |
| 446 | 541 | |
| 447 | 542 | /* |
| 448 | 543 | ** Append the wiki text for an remark to the end of the given BLOB. |
| 449 | 544 | */ |
| 450 | | -static void appendRemark(Blob *p){ |
| 545 | +static void appendRemark(Blob *p, const char *zMimetype){ |
| 451 | 546 | char *zDate; |
| 452 | 547 | const char *zUser; |
| 453 | 548 | const char *zRemark; |
| 454 | 549 | char *zId; |
| 455 | 550 | |
| 456 | 551 | zDate = db_text(0, "SELECT datetime('now')"); |
| 457 | | - zId = db_text(0, "SELECT lower(hex(randomblob(8)))"); |
| 458 | | - blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", |
| 459 | | - zId, zDate, g.zLogin); |
| 460 | | - free(zDate); |
| 552 | + zRemark = PD("r",""); |
| 461 | 553 | zUser = PD("u",g.zLogin); |
| 462 | | - if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){ |
| 463 | | - blob_appendf(p, " (claiming to be %h)", zUser); |
| 554 | + if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
| 555 | + zId = db_text(0, "SELECT lower(hex(randomblob(8)))"); |
| 556 | + blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", |
| 557 | + zId, zDate, g.zLogin); |
| 558 | + if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){ |
| 559 | + blob_appendf(p, " (claiming to be %h)", zUser); |
| 560 | + } |
| 561 | + blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId); |
| 562 | + }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
| 563 | + blob_appendf(p, "\n\n------\n*On %s UTC %h", zDate, g.zLogin); |
| 564 | + if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){ |
| 565 | + blob_appendf(p, " (claiming to be %h)", zUser); |
| 566 | + } |
| 567 | + blob_appendf(p, " added:*\n\n%s\n", zRemark); |
| 568 | + }else{ |
| 569 | + blob_appendf(p, "\n\n------------------------------------------------\n" |
| 570 | + "On %s UTC %s", zDate, g.zLogin); |
| 571 | + if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){ |
| 572 | + blob_appendf(p, " (claiming to be %s)", zUser); |
| 573 | + } |
| 574 | + blob_appendf(p, " added:\n\n%s\n", zRemark); |
| 464 | 575 | } |
| 465 | | - zRemark = PD("r",""); |
| 466 | | - blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId); |
| 576 | + fossil_free(zDate); |
| 467 | 577 | } |
| 468 | 578 | |
| 469 | 579 | /* |
| 470 | 580 | ** WEBPAGE: wikiappend |
| 471 | | -** URL: /wikiappend?name=PAGENAME |
| 581 | +** URL: /wikiappend?name=PAGENAME&mimetype=MIMETYPE |
| 472 | 582 | */ |
| 473 | 583 | void wikiappend_page(void){ |
| 474 | 584 | char *zTag; |
| 475 | 585 | int rid = 0; |
| 476 | 586 | int isSandbox; |
| 477 | 587 | const char *zPageName; |
| 478 | 588 | const char *zUser; |
| 589 | + const char *zMimetype; |
| 479 | 590 | int goodCaptcha = 1; |
| 591 | + const char *zFormat; |
| 480 | 592 | |
| 481 | 593 | login_check_credentials(); |
| 482 | 594 | zPageName = PD("name",""); |
| 595 | + zMimetype = wiki_filter_mimetypes(P("mimetype")); |
| 483 | 596 | if( check_name(zPageName) ) return; |
| 484 | 597 | isSandbox = is_sandbox(zPageName); |
| 485 | 598 | if( !isSandbox ){ |
| 486 | 599 | zTag = mprintf("wiki-%s", zPageName); |
| 487 | 600 | rid = db_int(0, |
| | @@ -509,11 +622,11 @@ |
| 509 | 622 | Manifest *pWiki = 0; |
| 510 | 623 | |
| 511 | 624 | blob_zero(&body); |
| 512 | 625 | if( isSandbox ){ |
| 513 | 626 | blob_appendf(&body, db_get("sandbox","")); |
| 514 | | - appendRemark(&body); |
| 627 | + appendRemark(&body, zMimetype); |
| 515 | 628 | db_set("sandbox", blob_str(&body), 0); |
| 516 | 629 | }else{ |
| 517 | 630 | login_verify_csrf_secret(); |
| 518 | 631 | pWiki = manifest_get(rid, CFTYPE_WIKI); |
| 519 | 632 | if( pWiki ){ |
| | @@ -523,19 +636,22 @@ |
| 523 | 636 | blob_zero(&wiki); |
| 524 | 637 | db_begin_transaction(); |
| 525 | 638 | zDate = date_in_standard_format("now"); |
| 526 | 639 | blob_appendf(&wiki, "D %s\n", zDate); |
| 527 | 640 | blob_appendf(&wiki, "L %F\n", zPageName); |
| 641 | + if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")!=0 ){ |
| 642 | + blob_appendf(&wiki, "N %s\n", zMimetype); |
| 643 | + } |
| 528 | 644 | if( rid ){ |
| 529 | 645 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 530 | 646 | blob_appendf(&wiki, "P %s\n", zUuid); |
| 531 | 647 | free(zUuid); |
| 532 | 648 | } |
| 533 | 649 | if( g.zLogin ){ |
| 534 | 650 | blob_appendf(&wiki, "U %F\n", g.zLogin); |
| 535 | 651 | } |
| 536 | | - appendRemark(&body); |
| 652 | + appendRemark(&body, zMimetype); |
| 537 | 653 | blob_appendf(&wiki, "W %d\n%s\n", blob_size(&body), blob_str(&body)); |
| 538 | 654 | md5sum_blob(&wiki, &cksum); |
| 539 | 655 | blob_appendf(&wiki, "Z %b\n", &cksum); |
| 540 | 656 | blob_reset(&cksum); |
| 541 | 657 | wiki_put(&wiki, rid); |
| | @@ -553,23 +669,25 @@ |
| 553 | 669 | @ <p class="generalError">Error: Incorrect security code.</p> |
| 554 | 670 | } |
| 555 | 671 | if( P("preview")!=0 ){ |
| 556 | 672 | Blob preview; |
| 557 | 673 | blob_zero(&preview); |
| 558 | | - appendRemark(&preview); |
| 674 | + appendRemark(&preview, zMimetype); |
| 559 | 675 | @ Preview:<hr> |
| 560 | | - wiki_convert(&preview, 0, 0); |
| 676 | + wiki_render_by_mimetype(&preview, zMimetype); |
| 561 | 677 | @ <hr> |
| 562 | 678 | blob_reset(&preview); |
| 563 | 679 | } |
| 564 | 680 | zUser = PD("u", g.zLogin); |
| 565 | 681 | form_begin(0, "%R/wikiappend"); |
| 566 | 682 | login_insert_csrf_secret(); |
| 567 | 683 | @ <input type="hidden" name="name" value="%h(zPageName)" /> |
| 684 | + @ <input type="hidden" name="mimetype" value="%h(zMimetype)" /> |
| 568 | 685 | @ Your Name: |
| 569 | 686 | @ <input type="text" name="u" size="20" value="%h(zUser)" /><br /> |
| 570 | | - @ Comment to append:<br /> |
| 687 | + zFormat = mimetype_common_name(zMimetype); |
| 688 | + @ Comment to append (formatted as %s(zFormat)):<br /> |
| 571 | 689 | @ <textarea name="r" class="wikiedit" cols="80" |
| 572 | 690 | @ rows="10" wrap="virtual">%h(PD("r",""))</textarea> |
| 573 | 691 | @ <br /> |
| 574 | 692 | @ <input type="submit" name="preview" value="Preview Your Comment" /> |
| 575 | 693 | @ <input type="submit" name="submit" value="Append Your Changes" /> |
| 576 | 694 | |