Fossil SCM

Instead of injecting multiple mini-scripts, queue up script code and emit it in one anonymous function at the end. Update the version number and permalink after saving so that we're basing on the new version.

stephan 2020-05-02 16:43 UTC checkin-without-checkout
Commit 6407b6ca37902e2c07ada139f63830c9139fb56340afeb07ed0f5d8f1df3ba14
2 files changed +158 -80 +1
+158 -80
--- src/checkin.c
+++ src/checkin.c
@@ -3586,10 +3586,17 @@
35863586
fossil_print("<script nonce='%s'>", style_nonce());
35873587
}else{
35883588
fossil_print("</script>\n");
35893589
}
35903590
}
3591
+
3592
+#if 0
3593
+/*
3594
+** This function is for potential TODO features for /fileedit.
3595
+** It's been tested with that code but is not currently used
3596
+** by it.
3597
+*/
35913598
static void fileedit_emit_script_fetch(){
35923599
#define fp fossil_print
35933600
fileedit_emit_script(0);
35943601
fp("window.fossilFetch = function(path,opt){\n");
35953602
fp(" if('function'===typeof opt){\n");
@@ -3612,16 +3619,28 @@
36123619
fp(" x.send();");
36133620
fp("};\n");
36143621
fileedit_emit_script(1);
36153622
#undef fp
36163623
};
3624
+#endif /* fileedit_emit_script_fetch() */
36173625
3618
-static void fileedit_checkbox(const char *zFieldName,
3619
- const char * zLabel,
3620
- const char * zValue,
3621
- const char * zTip,
3622
- int isChecked){
3626
+/*
3627
+** Outputs a labeled checkbox element:
3628
+**
3629
+** <span class='input-with-label' title={{zTip}}>
3630
+** <input type='checkbox' name={{zFieldName}} value={{zValue}}
3631
+** {{isChecked ? " checked : ""}}/>
3632
+** <span>{{zLabel}}</span>
3633
+** </span>
3634
+**
3635
+** zFieldName, zLabel, and zValue are required. zTip is optional.
3636
+*/
3637
+static void style_labeled_checkbox(const char *zFieldName,
3638
+ const char * zLabel,
3639
+ const char * zValue,
3640
+ const char * zTip,
3641
+ int isChecked){
36233642
fossil_print("<span class='input-with-label'");
36243643
if(zTip && *zTip){
36253644
fossil_print(" title='%h'", zTip);
36263645
}
36273646
fossil_print("><input type='checkbox' name='%s' value='%T'%s/>",
@@ -3651,29 +3670,36 @@
36513670
**
36523671
**
36533672
*/
36543673
void fileedit_page(){
36553674
const char * zFilename = PD("file",P("name")); /* filename */
3656
- const char * zRev = P("r"); /* checkin version */
3657
- const char * zContent = P("content"); /* file content */
3658
- const char * zComment = P("comment"); /* checkin comment */
3659
- CheckinMiniInfo cimi; /* Checkin state */
3660
- int submitMode = 0; /* See mapping below */
3661
- char * zRevResolved = 0; /* Resolved zRev */
3662
- int vid, newVid = 0; /* checkin rid */
3663
- char * zFileUuid = 0; /* File content UUID */
3664
- int frid = 0; /* File content rid */
3665
- Blob err = empty_blob; /* Error report */
3666
- const char * zFlagCheck = 0; /* Temp url flag holder */
3675
+ const char * zRev = P("r"); /* checkin version */
3676
+ const char * zContent = P("content"); /* file content */
3677
+ const char * zComment = P("comment"); /* checkin comment */
3678
+ CheckinMiniInfo cimi; /* Checkin state */
3679
+ int submitMode = 0; /* See mapping below */
3680
+ char * zRevResolved = 0; /* Resolved zRev */
3681
+ int vid, newVid = 0; /* checkin rid */
3682
+ char * zFileUuid = 0; /* File content UUID */
3683
+ int frid = 0; /* File content rid */
3684
+ Blob err = empty_blob; /* Error report */
3685
+ const char * zFlagCheck = 0; /* Temp url flag holder */
3686
+ Blob endScript = empty_blob; /* Script code to run at the
3687
+ end. This content will be
3688
+ combined into a single JS
3689
+ function call, thus each
3690
+ entry must end with a
3691
+ semicolon. */
36673692
Stmt stmt = empty_Stmt;
36683693
#define fail(EXPR) blob_appendf EXPR; goto end_footer
36693694
36703695
login_check_credentials();
36713696
if( !g.perm.Write ){
36723697
login_needed(g.anon.Write);
36733698
return;
36743699
}
3700
+ db_begin_transaction();
36753701
CheckinMiniInfo_init(&cimi);
36763702
submitMode = atoi(PD("submit","0"))
36773703
/* Submit modes: 0=initial request,
36783704
** 1=submit (save), 2=preview, 3=diff */;
36793705
zFlagCheck = P("comment");
@@ -3739,17 +3765,22 @@
37393765
37403766
/* All set. Here we go... */
37413767
#define fp fossil_print
37423768
/* ^^^ Appologies, Richard, but the @ form plays havoc with emacs */
37433769
3744
- fileedit_emit_script_fetch();
3745
-
3746
- fp("<h1>Editing: %h</h1>",zFilename);
3747
- fp("<p class='hint'>Permalink: "
3748
- "<a href='%R/fileedit?file=%T&r=%s'>"
3749
- "%R/fileedit?file=%T&r=%s</a></p>",
3750
- zFilename, zRev, zFilename, zRev);
3770
+ fp("<h1>Editing:</h1>");
3771
+ fp("<p class='hint'>");
3772
+ fp("File: <code>%h</code><br>"
3773
+ "Version: <code id='r-label'>%s</code><br>",
3774
+ zFilename, zRevResolved);
3775
+ fp("Permalink: <code>"
3776
+ "<a id='permalink' href='%R/fileedit?file=%T&r=%!S'>"
3777
+ "/fileedit?file=%T&r=%!S</a></code><br>"
3778
+ "(Clicking the permalink will reload the page and discard "
3779
+ "all edits!)",
3780
+ zFilename, zRevResolved, zFilename, zRevResolved);
3781
+ fp("</p>");
37513782
fp("<p>This page is <em>far from complete</em>.</p>\n");
37523783
37533784
fp("<form action='%R/fileedit' method='POST' "
37543785
"class='fileedit-form'>\n");
37553786
@@ -3757,66 +3788,69 @@
37573788
fp("<input type='hidden' name='r' value='%s'>", zRevResolved);
37583789
fp("<input type='hidden' name='file' value='%T'>",
37593790
zFilename);
37603791
37613792
/******* Comment *******/
3762
- fp("<h3>Comment</h3>\n");
3793
+ fp("<h3>Checkin Comment</h3>\n");
37633794
fp("<textarea name='comment' rows='3' cols='80'>");
37643795
if(zComment && *zComment){
37653796
fp("%h"/*%h? %s?*/, zComment);
37663797
}
37673798
fp("</textarea>\n");
37683799
fp("<div class='hint'>Comments use the Fossil wiki markup "
37693800
"syntax.</div>"/*TODO: radiobuttons for fossil/me/plain text*/);
37703801
37713802
/******* Content *******/
3772
- fp("<h3>Content</h3>\n");
3803
+ fp("<h3>File Content</h3>\n");
37733804
fp("<textarea name='content' id='fileedit-content' "
37743805
"rows='20' cols='80'>");
37753806
fp("Loading...");
37763807
fp("</textarea>\n");
37773808
/******* Flags/options *******/
37783809
fp("<fieldset class='fileedit-options'>"
3779
- "<legend>Many checkboxes are TODO</legend><div>"
3810
+ "<legend>Options</legend><div>"
37803811
/* Chrome does not sanely lay out multiple
37813812
** fieldset children after the <legend>, so
37823813
** a containing div is necessary. */);
37833814
/*
3784
- ** TODO: Put checkboxes here...
3785
- **
3786
- ** dry-run, convert-eol
3787
- ** date-override, allow-older (in case server time is
3788
- ** messed up or someone checked something in w/ a future timestamp),
3789
- ** prefer-delta, strongly-prefer-delta (undocumented - for
3790
- ** development/admin use only).
3815
+ ** TODO?: date-override date selection field. Maybe use
3816
+ ** an input[type=datetime-local].
37913817
*/
37923818
if(0==submitMode || P("dry_run")!=0){
37933819
cimi.flags |= CIMINI_DRY_RUN;
37943820
}
3795
- fileedit_checkbox("dry_run", "Dry-run?", "1",
3796
- "In dry-run mode, do not really save.",
3797
- cimi.flags & CIMINI_DRY_RUN);
3821
+ style_labeled_checkbox("dry_run", "Dry-run?", "1",
3822
+ "In dry-run mode, do not really save.",
3823
+ cimi.flags & CIMINI_DRY_RUN);
37983824
if(P("allow_fork")!=0){
37993825
cimi.flags |= CIMINI_ALLOW_FORK;
38003826
}
3801
- fileedit_checkbox("allow_fork", "Allow fork?", "1",
3802
- "Allow saving to create a fork?",
3803
- cimi.flags & CIMINI_ALLOW_FORK);
3827
+ style_labeled_checkbox("allow_fork", "Allow fork?", "1",
3828
+ "Allow saving to create a fork?",
3829
+ cimi.flags & CIMINI_ALLOW_FORK);
3830
+ if(P("allow_older")!=0){
3831
+ cimi.flags |= CIMINI_ALLOW_OLDER;
3832
+ }
3833
+ style_labeled_checkbox("allow_older", "Allow older?", "1",
3834
+ "Allow saving to a version which has "
3835
+ "a newer timestamp?",
3836
+ cimi.flags & CIMINI_ALLOW_OLDER);
38043837
if(P("exec_bit")!=0){
38053838
cimi.filePerm = PERM_EXE;
38063839
}
3807
- fileedit_checkbox("exec_bit", "Executable?", "1",
3808
- "Set executable bit?",
3809
- PERM_EXE==cimi.filePerm);
3840
+ style_labeled_checkbox("exec_bit", "Executable?", "1",
3841
+ "Set executable bit?",
3842
+ PERM_EXE==cimi.filePerm);
38103843
if(P("allow_merge_conflict")!=0){
38113844
cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
38123845
}
3813
- fileedit_checkbox("allow_merge_conflict",
3814
- "Allow merge conflict markers?", "1",
3815
- "Allow saving even if the content contains what "
3816
- "appear to be fossil merge conflict markers?",
3817
- cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
3846
+ style_labeled_checkbox("allow_merge_conflict",
3847
+ "Allow merge conflict markers?", "1",
3848
+ "Allow saving even if the content contains "
3849
+ "what appear to be fossil merge conflict "
3850
+ "markers?",
3851
+ cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
38183852
{/* EOL conversion policy... */
38193853
const int eolMode = submitMode==0 ? 0 : atoi(PD("eol","0"));
38203854
switch(eolMode){
38213855
case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break;
38223856
case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
@@ -3824,17 +3858,27 @@
38243858
}
38253859
fp("<select name='eol' "
38263860
"title='EOL conversion policy, noting that form-processing "
38273861
"may implicitly change the line endings of the input.'>");
38283862
fp("<option value='0'%s>Inherit EOLs</option>",
3829
- eolMode==0 ? " selected" : "");
3863
+ (eolMode!=1 && eolMode!=2) ? " selected" : "");
38303864
fp("<option value='1'%s/>Unix EOLs</option>",
38313865
eolMode==1 ? " selected" : "");
38323866
fp("<option value='2'%s>Windows EOLs</option>",
38333867
eolMode==2 ? " selected" : "");
38343868
fp("</select>");
38353869
}
3870
+ if(P("prefer_delta")!=0){
3871
+ cimi.flags |= CIMINI_PREFER_DELTA;
3872
+ }
3873
+ style_labeled_checkbox("prefer_delta",
3874
+ "Prefer delta manifest?", "1",
3875
+ "Will create a delta manifest, instead of "
3876
+ "baseline, if conditions are favorable to do "
3877
+ "so. This option is only a suggestion.",
3878
+ cimi.flags & CIMINI_PREFER_DELTA);
3879
+
38363880
fp("</div></fieldset>") /* end of checkboxes */;
38373881
38383882
/******* Buttons *******/
38393883
fp("<fieldset class='fileedit-options'>"
38403884
"<legend>Several buttons are TODO</legend><div>");
@@ -3847,68 +3891,89 @@
38473891
fp("</div></fieldset>");
38483892
38493893
/******* End of form *******/
38503894
fp("</form>\n");
38513895
3852
- /* Populate doc...
3853
- ** To avoid all escaping-related issues, we have to do this one
3854
- ** of two ways:
3855
- **
3856
- ** 1) Fetch the content via AJAX. That only works if the content
3857
- ** is already in the db, but not for edited versions.
3858
- **
3859
- ** 2) Store the content as JSON and feed it into the textarea
3860
- ** using JavaScript.
3861
- */
3862
- fileedit_emit_script(0);
3863
- {
3896
+ {
3897
+ /* Populate the editor...
3898
+ **
3899
+ ** To avoid all escaping-related issues, we have to do this one of
3900
+ ** two ways:
3901
+ **
3902
+ ** 1) Fetch the content via AJAX. That only works if the content
3903
+ ** is already in the db, but not for edited versions.
3904
+ **
3905
+ ** 2) Store the content as JSON and feed it into the textarea
3906
+ ** using JavaScript.
3907
+ */
38643908
char const * zQuoted = 0;
38653909
if(blob_size(&cimi.fileContent)>0){
3866
- db_prepare(&stmt, "SELECT json_quote(%B)",&cimi.fileContent);
3910
+ db_prepare(&stmt, "SELECT json_quote(%B)", &cimi.fileContent);
38673911
db_step(&stmt);
38683912
zQuoted = db_column_text(&stmt,0);
38693913
}
3870
- fp("document.getElementById('fileedit-content').value=%s;",
3871
- zQuoted ? zQuoted : "''");
3914
+ blob_appendf(&endScript,
3915
+ "/* populate editor form */\n"
3916
+ "document.getElementById('fileedit-content')"
3917
+ ".value=%s;", zQuoted ? zQuoted : "'';\n");
38723918
if(stmt.pStmt){
38733919
db_finalize(&stmt);
38743920
}
38753921
}
3876
- fileedit_emit_script(1);
3877
-
3878
- zContent = 0;
3879
- fossil_free(zRevResolved);
3880
- fossil_free(zFileUuid);
38813922
38823923
if(1==submitMode/*save*/){
38833924
Blob manifest = empty_blob;
3884
- int rc;
3885
-
3886
- /* TODO: pull these flags from P() */
3887
- cimi.flags |= CIMINI_PREFER_DELTA;
3925
+ char * zNewUuid = 0;
38883926
/*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/
3889
-
3890
- db_begin_transaction();
3927
+ if(zComment && *zComment){
3928
+ blob_append(&cimi.comment, zComment, -1);
3929
+ }else{
3930
+ fail((&err,"Empty comment is not permitted."));
3931
+ }
38913932
cimi.pParent = manifest_get(vid, CFTYPE_MANIFEST, 0);
38923933
assert(cimi.pParent && "We know vid is valid.");
38933934
cimi.zFilename = mprintf("%s",zFilename);
38943935
cimi.pMfOut = &manifest;
3895
- if(zComment && *zComment){
3896
- blob_append(&cimi.comment, zComment, -1);
3897
- }
3898
- rc = checkin_mini(&cimi, &newVid, &err);
3936
+ checkin_mini(&cimi, &newVid, &err);
38993937
if(newVid!=0){
3900
- char * zNewUuid = rid_to_uuid(newVid);
3938
+ zNewUuid = rid_to_uuid(newVid);
39013939
fp("<h3>Manifest%s: %S</h3><pre>"
39023940
"<code class='fileedit-manifest'>%h</code>"
39033941
"</pre>",
39043942
(cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "",
39053943
zNewUuid, blob_str(&manifest));
3944
+ if(!(CIMINI_DRY_RUN & cimi.flags)){
3945
+ /* We need to update certain form fields and UI elements so
3946
+ ** they're not left pointing to the previous version. While
3947
+ ** we're at it, we'll re-enable dry-run mode for sanity's
3948
+ ** sake.
3949
+ */
3950
+ blob_appendf(&endScript,
3951
+ "/* Toggle dry-run back on */\n"
3952
+ "document.querySelector('input[type=checkbox]"
3953
+ "[name=dry_run]').checked=true;\n");
3954
+ blob_appendf(&endScript,
3955
+ "/* Update version number */\n"
3956
+ "document.querySelector('input[name=r]')"
3957
+ ".value=%Q;\n"
3958
+ "document.querySelector('#r-label')"
3959
+ ".innerText=%Q;\n",
3960
+ zNewUuid, zNewUuid);
3961
+ blob_appendf(&endScript,
3962
+ "/* Update permalink */\n"
3963
+ "const urlFull='%R/fileedit?file=%T&r=%!S';\n"
3964
+ "const urlShort='/fileedit?file=%T&r=%!S';\n"
3965
+ "let link=document.querySelector('#permalink');\n"
3966
+ "link.innerText=urlShort;\n"
3967
+ "link.setAttribute('href',urlFull);\n",
3968
+ zFilename, zNewUuid, zFilename, zNewUuid);
3969
+ }
39063970
fossil_free(zNewUuid);
39073971
}
3972
+ /* On error, the error message is in the err blob and will
3973
+ ** be emitted below. */
39083974
blob_reset(&manifest);
3909
- db_end_transaction(rc ? 0 : 1);
39103975
}else if(2==submitMode/*preview*/){
39113976
/* TODO */
39123977
fail((&err,"Preview mode is still TODO."));
39133978
}else if(3==submitMode/*diff*/){
39143979
fail((&err,"Diff mode is still TODO."));
@@ -3916,17 +3981,30 @@
39163981
/* Ignore invalid submitMode value */
39173982
goto end_footer;
39183983
}
39193984
39203985
end_footer:
3986
+ zContent = 0;
3987
+ fossil_free(zRevResolved);
3988
+ fossil_free(zFileUuid);
39213989
if(stmt.pStmt){
39223990
db_finalize(&stmt);
39233991
}
39243992
if(blob_size(&err)){
39253993
fp("<div class='fileedit-error-report'>%h</div>",
39263994
blob_str(&err));
39273995
}
39283996
blob_reset(&err);
39293997
CheckinMiniInfo_cleanup(&cimi);
3998
+ if(blob_size(&endScript)>0){
3999
+ fileedit_emit_script(0);
4000
+ fp("(function(){\n");
4001
+ fp("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
4002
+ &endScript);
4003
+ fp("})();");
4004
+ fileedit_emit_script(1);
4005
+ }
4006
+ db_end_transaction(0/*noting that dry-run mode will have already
4007
+ ** set this to rollback mode. */);
39304008
style_footer();
39314009
#undef fp
39324010
}
39334011
--- src/checkin.c
+++ src/checkin.c
@@ -3586,10 +3586,17 @@
3586 fossil_print("<script nonce='%s'>", style_nonce());
3587 }else{
3588 fossil_print("</script>\n");
3589 }
3590 }
 
 
 
 
 
 
 
3591 static void fileedit_emit_script_fetch(){
3592 #define fp fossil_print
3593 fileedit_emit_script(0);
3594 fp("window.fossilFetch = function(path,opt){\n");
3595 fp(" if('function'===typeof opt){\n");
@@ -3612,16 +3619,28 @@
3612 fp(" x.send();");
3613 fp("};\n");
3614 fileedit_emit_script(1);
3615 #undef fp
3616 };
 
3617
3618 static void fileedit_checkbox(const char *zFieldName,
3619 const char * zLabel,
3620 const char * zValue,
3621 const char * zTip,
3622 int isChecked){
 
 
 
 
 
 
 
 
 
 
 
3623 fossil_print("<span class='input-with-label'");
3624 if(zTip && *zTip){
3625 fossil_print(" title='%h'", zTip);
3626 }
3627 fossil_print("><input type='checkbox' name='%s' value='%T'%s/>",
@@ -3651,29 +3670,36 @@
3651 **
3652 **
3653 */
3654 void fileedit_page(){
3655 const char * zFilename = PD("file",P("name")); /* filename */
3656 const char * zRev = P("r"); /* checkin version */
3657 const char * zContent = P("content"); /* file content */
3658 const char * zComment = P("comment"); /* checkin comment */
3659 CheckinMiniInfo cimi; /* Checkin state */
3660 int submitMode = 0; /* See mapping below */
3661 char * zRevResolved = 0; /* Resolved zRev */
3662 int vid, newVid = 0; /* checkin rid */
3663 char * zFileUuid = 0; /* File content UUID */
3664 int frid = 0; /* File content rid */
3665 Blob err = empty_blob; /* Error report */
3666 const char * zFlagCheck = 0; /* Temp url flag holder */
 
 
 
 
 
 
3667 Stmt stmt = empty_Stmt;
3668 #define fail(EXPR) blob_appendf EXPR; goto end_footer
3669
3670 login_check_credentials();
3671 if( !g.perm.Write ){
3672 login_needed(g.anon.Write);
3673 return;
3674 }
 
3675 CheckinMiniInfo_init(&cimi);
3676 submitMode = atoi(PD("submit","0"))
3677 /* Submit modes: 0=initial request,
3678 ** 1=submit (save), 2=preview, 3=diff */;
3679 zFlagCheck = P("comment");
@@ -3739,17 +3765,22 @@
3739
3740 /* All set. Here we go... */
3741 #define fp fossil_print
3742 /* ^^^ Appologies, Richard, but the @ form plays havoc with emacs */
3743
3744 fileedit_emit_script_fetch();
3745
3746 fp("<h1>Editing: %h</h1>",zFilename);
3747 fp("<p class='hint'>Permalink: "
3748 "<a href='%R/fileedit?file=%T&r=%s'>"
3749 "%R/fileedit?file=%T&r=%s</a></p>",
3750 zFilename, zRev, zFilename, zRev);
 
 
 
 
 
3751 fp("<p>This page is <em>far from complete</em>.</p>\n");
3752
3753 fp("<form action='%R/fileedit' method='POST' "
3754 "class='fileedit-form'>\n");
3755
@@ -3757,66 +3788,69 @@
3757 fp("<input type='hidden' name='r' value='%s'>", zRevResolved);
3758 fp("<input type='hidden' name='file' value='%T'>",
3759 zFilename);
3760
3761 /******* Comment *******/
3762 fp("<h3>Comment</h3>\n");
3763 fp("<textarea name='comment' rows='3' cols='80'>");
3764 if(zComment && *zComment){
3765 fp("%h"/*%h? %s?*/, zComment);
3766 }
3767 fp("</textarea>\n");
3768 fp("<div class='hint'>Comments use the Fossil wiki markup "
3769 "syntax.</div>"/*TODO: radiobuttons for fossil/me/plain text*/);
3770
3771 /******* Content *******/
3772 fp("<h3>Content</h3>\n");
3773 fp("<textarea name='content' id='fileedit-content' "
3774 "rows='20' cols='80'>");
3775 fp("Loading...");
3776 fp("</textarea>\n");
3777 /******* Flags/options *******/
3778 fp("<fieldset class='fileedit-options'>"
3779 "<legend>Many checkboxes are TODO</legend><div>"
3780 /* Chrome does not sanely lay out multiple
3781 ** fieldset children after the <legend>, so
3782 ** a containing div is necessary. */);
3783 /*
3784 ** TODO: Put checkboxes here...
3785 **
3786 ** dry-run, convert-eol
3787 ** date-override, allow-older (in case server time is
3788 ** messed up or someone checked something in w/ a future timestamp),
3789 ** prefer-delta, strongly-prefer-delta (undocumented - for
3790 ** development/admin use only).
3791 */
3792 if(0==submitMode || P("dry_run")!=0){
3793 cimi.flags |= CIMINI_DRY_RUN;
3794 }
3795 fileedit_checkbox("dry_run", "Dry-run?", "1",
3796 "In dry-run mode, do not really save.",
3797 cimi.flags & CIMINI_DRY_RUN);
3798 if(P("allow_fork")!=0){
3799 cimi.flags |= CIMINI_ALLOW_FORK;
3800 }
3801 fileedit_checkbox("allow_fork", "Allow fork?", "1",
3802 "Allow saving to create a fork?",
3803 cimi.flags & CIMINI_ALLOW_FORK);
 
 
 
 
 
 
 
3804 if(P("exec_bit")!=0){
3805 cimi.filePerm = PERM_EXE;
3806 }
3807 fileedit_checkbox("exec_bit", "Executable?", "1",
3808 "Set executable bit?",
3809 PERM_EXE==cimi.filePerm);
3810 if(P("allow_merge_conflict")!=0){
3811 cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
3812 }
3813 fileedit_checkbox("allow_merge_conflict",
3814 "Allow merge conflict markers?", "1",
3815 "Allow saving even if the content contains what "
3816 "appear to be fossil merge conflict markers?",
3817 cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
 
3818 {/* EOL conversion policy... */
3819 const int eolMode = submitMode==0 ? 0 : atoi(PD("eol","0"));
3820 switch(eolMode){
3821 case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break;
3822 case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
@@ -3824,17 +3858,27 @@
3824 }
3825 fp("<select name='eol' "
3826 "title='EOL conversion policy, noting that form-processing "
3827 "may implicitly change the line endings of the input.'>");
3828 fp("<option value='0'%s>Inherit EOLs</option>",
3829 eolMode==0 ? " selected" : "");
3830 fp("<option value='1'%s/>Unix EOLs</option>",
3831 eolMode==1 ? " selected" : "");
3832 fp("<option value='2'%s>Windows EOLs</option>",
3833 eolMode==2 ? " selected" : "");
3834 fp("</select>");
3835 }
 
 
 
 
 
 
 
 
 
 
3836 fp("</div></fieldset>") /* end of checkboxes */;
3837
3838 /******* Buttons *******/
3839 fp("<fieldset class='fileedit-options'>"
3840 "<legend>Several buttons are TODO</legend><div>");
@@ -3847,68 +3891,89 @@
3847 fp("</div></fieldset>");
3848
3849 /******* End of form *******/
3850 fp("</form>\n");
3851
3852 /* Populate doc...
3853 ** To avoid all escaping-related issues, we have to do this one
3854 ** of two ways:
3855 **
3856 ** 1) Fetch the content via AJAX. That only works if the content
3857 ** is already in the db, but not for edited versions.
3858 **
3859 ** 2) Store the content as JSON and feed it into the textarea
3860 ** using JavaScript.
3861 */
3862 fileedit_emit_script(0);
3863 {
3864 char const * zQuoted = 0;
3865 if(blob_size(&cimi.fileContent)>0){
3866 db_prepare(&stmt, "SELECT json_quote(%B)",&cimi.fileContent);
3867 db_step(&stmt);
3868 zQuoted = db_column_text(&stmt,0);
3869 }
3870 fp("document.getElementById('fileedit-content').value=%s;",
3871 zQuoted ? zQuoted : "''");
 
 
3872 if(stmt.pStmt){
3873 db_finalize(&stmt);
3874 }
3875 }
3876 fileedit_emit_script(1);
3877
3878 zContent = 0;
3879 fossil_free(zRevResolved);
3880 fossil_free(zFileUuid);
3881
3882 if(1==submitMode/*save*/){
3883 Blob manifest = empty_blob;
3884 int rc;
3885
3886 /* TODO: pull these flags from P() */
3887 cimi.flags |= CIMINI_PREFER_DELTA;
3888 /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/
3889
3890 db_begin_transaction();
 
 
 
3891 cimi.pParent = manifest_get(vid, CFTYPE_MANIFEST, 0);
3892 assert(cimi.pParent && "We know vid is valid.");
3893 cimi.zFilename = mprintf("%s",zFilename);
3894 cimi.pMfOut = &manifest;
3895 if(zComment && *zComment){
3896 blob_append(&cimi.comment, zComment, -1);
3897 }
3898 rc = checkin_mini(&cimi, &newVid, &err);
3899 if(newVid!=0){
3900 char * zNewUuid = rid_to_uuid(newVid);
3901 fp("<h3>Manifest%s: %S</h3><pre>"
3902 "<code class='fileedit-manifest'>%h</code>"
3903 "</pre>",
3904 (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "",
3905 zNewUuid, blob_str(&manifest));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3906 fossil_free(zNewUuid);
3907 }
 
 
3908 blob_reset(&manifest);
3909 db_end_transaction(rc ? 0 : 1);
3910 }else if(2==submitMode/*preview*/){
3911 /* TODO */
3912 fail((&err,"Preview mode is still TODO."));
3913 }else if(3==submitMode/*diff*/){
3914 fail((&err,"Diff mode is still TODO."));
@@ -3916,17 +3981,30 @@
3916 /* Ignore invalid submitMode value */
3917 goto end_footer;
3918 }
3919
3920 end_footer:
 
 
 
3921 if(stmt.pStmt){
3922 db_finalize(&stmt);
3923 }
3924 if(blob_size(&err)){
3925 fp("<div class='fileedit-error-report'>%h</div>",
3926 blob_str(&err));
3927 }
3928 blob_reset(&err);
3929 CheckinMiniInfo_cleanup(&cimi);
 
 
 
 
 
 
 
 
 
 
3930 style_footer();
3931 #undef fp
3932 }
3933
--- src/checkin.c
+++ src/checkin.c
@@ -3586,10 +3586,17 @@
3586 fossil_print("<script nonce='%s'>", style_nonce());
3587 }else{
3588 fossil_print("</script>\n");
3589 }
3590 }
3591
3592 #if 0
3593 /*
3594 ** This function is for potential TODO features for /fileedit.
3595 ** It's been tested with that code but is not currently used
3596 ** by it.
3597 */
3598 static void fileedit_emit_script_fetch(){
3599 #define fp fossil_print
3600 fileedit_emit_script(0);
3601 fp("window.fossilFetch = function(path,opt){\n");
3602 fp(" if('function'===typeof opt){\n");
@@ -3612,16 +3619,28 @@
3619 fp(" x.send();");
3620 fp("};\n");
3621 fileedit_emit_script(1);
3622 #undef fp
3623 };
3624 #endif /* fileedit_emit_script_fetch() */
3625
3626 /*
3627 ** Outputs a labeled checkbox element:
3628 **
3629 ** <span class='input-with-label' title={{zTip}}>
3630 ** <input type='checkbox' name={{zFieldName}} value={{zValue}}
3631 ** {{isChecked ? " checked : ""}}/>
3632 ** <span>{{zLabel}}</span>
3633 ** </span>
3634 **
3635 ** zFieldName, zLabel, and zValue are required. zTip is optional.
3636 */
3637 static void style_labeled_checkbox(const char *zFieldName,
3638 const char * zLabel,
3639 const char * zValue,
3640 const char * zTip,
3641 int isChecked){
3642 fossil_print("<span class='input-with-label'");
3643 if(zTip && *zTip){
3644 fossil_print(" title='%h'", zTip);
3645 }
3646 fossil_print("><input type='checkbox' name='%s' value='%T'%s/>",
@@ -3651,29 +3670,36 @@
3670 **
3671 **
3672 */
3673 void fileedit_page(){
3674 const char * zFilename = PD("file",P("name")); /* filename */
3675 const char * zRev = P("r"); /* checkin version */
3676 const char * zContent = P("content"); /* file content */
3677 const char * zComment = P("comment"); /* checkin comment */
3678 CheckinMiniInfo cimi; /* Checkin state */
3679 int submitMode = 0; /* See mapping below */
3680 char * zRevResolved = 0; /* Resolved zRev */
3681 int vid, newVid = 0; /* checkin rid */
3682 char * zFileUuid = 0; /* File content UUID */
3683 int frid = 0; /* File content rid */
3684 Blob err = empty_blob; /* Error report */
3685 const char * zFlagCheck = 0; /* Temp url flag holder */
3686 Blob endScript = empty_blob; /* Script code to run at the
3687 end. This content will be
3688 combined into a single JS
3689 function call, thus each
3690 entry must end with a
3691 semicolon. */
3692 Stmt stmt = empty_Stmt;
3693 #define fail(EXPR) blob_appendf EXPR; goto end_footer
3694
3695 login_check_credentials();
3696 if( !g.perm.Write ){
3697 login_needed(g.anon.Write);
3698 return;
3699 }
3700 db_begin_transaction();
3701 CheckinMiniInfo_init(&cimi);
3702 submitMode = atoi(PD("submit","0"))
3703 /* Submit modes: 0=initial request,
3704 ** 1=submit (save), 2=preview, 3=diff */;
3705 zFlagCheck = P("comment");
@@ -3739,17 +3765,22 @@
3765
3766 /* All set. Here we go... */
3767 #define fp fossil_print
3768 /* ^^^ Appologies, Richard, but the @ form plays havoc with emacs */
3769
3770 fp("<h1>Editing:</h1>");
3771 fp("<p class='hint'>");
3772 fp("File: <code>%h</code><br>"
3773 "Version: <code id='r-label'>%s</code><br>",
3774 zFilename, zRevResolved);
3775 fp("Permalink: <code>"
3776 "<a id='permalink' href='%R/fileedit?file=%T&r=%!S'>"
3777 "/fileedit?file=%T&r=%!S</a></code><br>"
3778 "(Clicking the permalink will reload the page and discard "
3779 "all edits!)",
3780 zFilename, zRevResolved, zFilename, zRevResolved);
3781 fp("</p>");
3782 fp("<p>This page is <em>far from complete</em>.</p>\n");
3783
3784 fp("<form action='%R/fileedit' method='POST' "
3785 "class='fileedit-form'>\n");
3786
@@ -3757,66 +3788,69 @@
3788 fp("<input type='hidden' name='r' value='%s'>", zRevResolved);
3789 fp("<input type='hidden' name='file' value='%T'>",
3790 zFilename);
3791
3792 /******* Comment *******/
3793 fp("<h3>Checkin Comment</h3>\n");
3794 fp("<textarea name='comment' rows='3' cols='80'>");
3795 if(zComment && *zComment){
3796 fp("%h"/*%h? %s?*/, zComment);
3797 }
3798 fp("</textarea>\n");
3799 fp("<div class='hint'>Comments use the Fossil wiki markup "
3800 "syntax.</div>"/*TODO: radiobuttons for fossil/me/plain text*/);
3801
3802 /******* Content *******/
3803 fp("<h3>File Content</h3>\n");
3804 fp("<textarea name='content' id='fileedit-content' "
3805 "rows='20' cols='80'>");
3806 fp("Loading...");
3807 fp("</textarea>\n");
3808 /******* Flags/options *******/
3809 fp("<fieldset class='fileedit-options'>"
3810 "<legend>Options</legend><div>"
3811 /* Chrome does not sanely lay out multiple
3812 ** fieldset children after the <legend>, so
3813 ** a containing div is necessary. */);
3814 /*
3815 ** TODO?: date-override date selection field. Maybe use
3816 ** an input[type=datetime-local].
 
 
 
 
 
3817 */
3818 if(0==submitMode || P("dry_run")!=0){
3819 cimi.flags |= CIMINI_DRY_RUN;
3820 }
3821 style_labeled_checkbox("dry_run", "Dry-run?", "1",
3822 "In dry-run mode, do not really save.",
3823 cimi.flags & CIMINI_DRY_RUN);
3824 if(P("allow_fork")!=0){
3825 cimi.flags |= CIMINI_ALLOW_FORK;
3826 }
3827 style_labeled_checkbox("allow_fork", "Allow fork?", "1",
3828 "Allow saving to create a fork?",
3829 cimi.flags & CIMINI_ALLOW_FORK);
3830 if(P("allow_older")!=0){
3831 cimi.flags |= CIMINI_ALLOW_OLDER;
3832 }
3833 style_labeled_checkbox("allow_older", "Allow older?", "1",
3834 "Allow saving to a version which has "
3835 "a newer timestamp?",
3836 cimi.flags & CIMINI_ALLOW_OLDER);
3837 if(P("exec_bit")!=0){
3838 cimi.filePerm = PERM_EXE;
3839 }
3840 style_labeled_checkbox("exec_bit", "Executable?", "1",
3841 "Set executable bit?",
3842 PERM_EXE==cimi.filePerm);
3843 if(P("allow_merge_conflict")!=0){
3844 cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
3845 }
3846 style_labeled_checkbox("allow_merge_conflict",
3847 "Allow merge conflict markers?", "1",
3848 "Allow saving even if the content contains "
3849 "what appear to be fossil merge conflict "
3850 "markers?",
3851 cimi.flags & CIMINI_ALLOW_MERGE_MARKER);
3852 {/* EOL conversion policy... */
3853 const int eolMode = submitMode==0 ? 0 : atoi(PD("eol","0"));
3854 switch(eolMode){
3855 case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break;
3856 case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
@@ -3824,17 +3858,27 @@
3858 }
3859 fp("<select name='eol' "
3860 "title='EOL conversion policy, noting that form-processing "
3861 "may implicitly change the line endings of the input.'>");
3862 fp("<option value='0'%s>Inherit EOLs</option>",
3863 (eolMode!=1 && eolMode!=2) ? " selected" : "");
3864 fp("<option value='1'%s/>Unix EOLs</option>",
3865 eolMode==1 ? " selected" : "");
3866 fp("<option value='2'%s>Windows EOLs</option>",
3867 eolMode==2 ? " selected" : "");
3868 fp("</select>");
3869 }
3870 if(P("prefer_delta")!=0){
3871 cimi.flags |= CIMINI_PREFER_DELTA;
3872 }
3873 style_labeled_checkbox("prefer_delta",
3874 "Prefer delta manifest?", "1",
3875 "Will create a delta manifest, instead of "
3876 "baseline, if conditions are favorable to do "
3877 "so. This option is only a suggestion.",
3878 cimi.flags & CIMINI_PREFER_DELTA);
3879
3880 fp("</div></fieldset>") /* end of checkboxes */;
3881
3882 /******* Buttons *******/
3883 fp("<fieldset class='fileedit-options'>"
3884 "<legend>Several buttons are TODO</legend><div>");
@@ -3847,68 +3891,89 @@
3891 fp("</div></fieldset>");
3892
3893 /******* End of form *******/
3894 fp("</form>\n");
3895
3896 {
3897 /* Populate the editor...
3898 **
3899 ** To avoid all escaping-related issues, we have to do this one of
3900 ** two ways:
3901 **
3902 ** 1) Fetch the content via AJAX. That only works if the content
3903 ** is already in the db, but not for edited versions.
3904 **
3905 ** 2) Store the content as JSON and feed it into the textarea
3906 ** using JavaScript.
3907 */
3908 char const * zQuoted = 0;
3909 if(blob_size(&cimi.fileContent)>0){
3910 db_prepare(&stmt, "SELECT json_quote(%B)", &cimi.fileContent);
3911 db_step(&stmt);
3912 zQuoted = db_column_text(&stmt,0);
3913 }
3914 blob_appendf(&endScript,
3915 "/* populate editor form */\n"
3916 "document.getElementById('fileedit-content')"
3917 ".value=%s;", zQuoted ? zQuoted : "'';\n");
3918 if(stmt.pStmt){
3919 db_finalize(&stmt);
3920 }
3921 }
 
 
 
 
 
3922
3923 if(1==submitMode/*save*/){
3924 Blob manifest = empty_blob;
3925 char * zNewUuid = 0;
 
 
 
3926 /*cimi.flags |= CIMINI_STRONGLY_PREFER_DELTA;*/
3927 if(zComment && *zComment){
3928 blob_append(&cimi.comment, zComment, -1);
3929 }else{
3930 fail((&err,"Empty comment is not permitted."));
3931 }
3932 cimi.pParent = manifest_get(vid, CFTYPE_MANIFEST, 0);
3933 assert(cimi.pParent && "We know vid is valid.");
3934 cimi.zFilename = mprintf("%s",zFilename);
3935 cimi.pMfOut = &manifest;
3936 checkin_mini(&cimi, &newVid, &err);
 
 
 
3937 if(newVid!=0){
3938 zNewUuid = rid_to_uuid(newVid);
3939 fp("<h3>Manifest%s: %S</h3><pre>"
3940 "<code class='fileedit-manifest'>%h</code>"
3941 "</pre>",
3942 (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "",
3943 zNewUuid, blob_str(&manifest));
3944 if(!(CIMINI_DRY_RUN & cimi.flags)){
3945 /* We need to update certain form fields and UI elements so
3946 ** they're not left pointing to the previous version. While
3947 ** we're at it, we'll re-enable dry-run mode for sanity's
3948 ** sake.
3949 */
3950 blob_appendf(&endScript,
3951 "/* Toggle dry-run back on */\n"
3952 "document.querySelector('input[type=checkbox]"
3953 "[name=dry_run]').checked=true;\n");
3954 blob_appendf(&endScript,
3955 "/* Update version number */\n"
3956 "document.querySelector('input[name=r]')"
3957 ".value=%Q;\n"
3958 "document.querySelector('#r-label')"
3959 ".innerText=%Q;\n",
3960 zNewUuid, zNewUuid);
3961 blob_appendf(&endScript,
3962 "/* Update permalink */\n"
3963 "const urlFull='%R/fileedit?file=%T&r=%!S';\n"
3964 "const urlShort='/fileedit?file=%T&r=%!S';\n"
3965 "let link=document.querySelector('#permalink');\n"
3966 "link.innerText=urlShort;\n"
3967 "link.setAttribute('href',urlFull);\n",
3968 zFilename, zNewUuid, zFilename, zNewUuid);
3969 }
3970 fossil_free(zNewUuid);
3971 }
3972 /* On error, the error message is in the err blob and will
3973 ** be emitted below. */
3974 blob_reset(&manifest);
 
3975 }else if(2==submitMode/*preview*/){
3976 /* TODO */
3977 fail((&err,"Preview mode is still TODO."));
3978 }else if(3==submitMode/*diff*/){
3979 fail((&err,"Diff mode is still TODO."));
@@ -3916,17 +3981,30 @@
3981 /* Ignore invalid submitMode value */
3982 goto end_footer;
3983 }
3984
3985 end_footer:
3986 zContent = 0;
3987 fossil_free(zRevResolved);
3988 fossil_free(zFileUuid);
3989 if(stmt.pStmt){
3990 db_finalize(&stmt);
3991 }
3992 if(blob_size(&err)){
3993 fp("<div class='fileedit-error-report'>%h</div>",
3994 blob_str(&err));
3995 }
3996 blob_reset(&err);
3997 CheckinMiniInfo_cleanup(&cimi);
3998 if(blob_size(&endScript)>0){
3999 fileedit_emit_script(0);
4000 fp("(function(){\n");
4001 fp("try{\n%b\n}catch(e){console.error('Exception:',e)}\n",
4002 &endScript);
4003 fp("})();");
4004 fileedit_emit_script(1);
4005 }
4006 db_end_transaction(0/*noting that dry-run mode will have already
4007 ** set this to rollback mode. */);
4008 style_footer();
4009 #undef fp
4010 }
4011
--- src/default_css.txt
+++ src/default_css.txt
@@ -898,10 +898,11 @@
898898
border: 1px inset #808080;
899899
border-radius: 0.5em;
900900
padding: 0.25em 0.4em;
901901
margin: 0 0.5em;
902902
display: inline-block;
903
+ cursor: pointer;
903904
}
904905
.input-with-label > input {
905906
margin: 0;
906907
}
907908
.input-with-label > input[type=checkbox] {
908909
--- src/default_css.txt
+++ src/default_css.txt
@@ -898,10 +898,11 @@
898 border: 1px inset #808080;
899 border-radius: 0.5em;
900 padding: 0.25em 0.4em;
901 margin: 0 0.5em;
902 display: inline-block;
 
903 }
904 .input-with-label > input {
905 margin: 0;
906 }
907 .input-with-label > input[type=checkbox] {
908
--- src/default_css.txt
+++ src/default_css.txt
@@ -898,10 +898,11 @@
898 border: 1px inset #808080;
899 border-radius: 0.5em;
900 padding: 0.25em 0.4em;
901 margin: 0 0.5em;
902 display: inline-block;
903 cursor: pointer;
904 }
905 .input-with-label > input {
906 margin: 0;
907 }
908 .input-with-label > input[type=checkbox] {
909

Keyboard Shortcuts

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