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.
Commit
6407b6ca37902e2c07ada139f63830c9139fb56340afeb07ed0f5d8f1df3ba14
Parent
33861414aefee47…
2 files changed
+158
-80
+1
+158
-80
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -3586,10 +3586,17 @@ | ||
| 3586 | 3586 | fossil_print("<script nonce='%s'>", style_nonce()); |
| 3587 | 3587 | }else{ |
| 3588 | 3588 | fossil_print("</script>\n"); |
| 3589 | 3589 | } |
| 3590 | 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 | +*/ | |
| 3591 | 3598 | static void fileedit_emit_script_fetch(){ |
| 3592 | 3599 | #define fp fossil_print |
| 3593 | 3600 | fileedit_emit_script(0); |
| 3594 | 3601 | fp("window.fossilFetch = function(path,opt){\n"); |
| 3595 | 3602 | fp(" if('function'===typeof opt){\n"); |
| @@ -3612,16 +3619,28 @@ | ||
| 3612 | 3619 | fp(" x.send();"); |
| 3613 | 3620 | fp("};\n"); |
| 3614 | 3621 | fileedit_emit_script(1); |
| 3615 | 3622 | #undef fp |
| 3616 | 3623 | }; |
| 3624 | +#endif /* fileedit_emit_script_fetch() */ | |
| 3617 | 3625 | |
| 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){ | |
| 3623 | 3642 | fossil_print("<span class='input-with-label'"); |
| 3624 | 3643 | if(zTip && *zTip){ |
| 3625 | 3644 | fossil_print(" title='%h'", zTip); |
| 3626 | 3645 | } |
| 3627 | 3646 | fossil_print("><input type='checkbox' name='%s' value='%T'%s/>", |
| @@ -3651,29 +3670,36 @@ | ||
| 3651 | 3670 | ** |
| 3652 | 3671 | ** |
| 3653 | 3672 | */ |
| 3654 | 3673 | void fileedit_page(){ |
| 3655 | 3674 | 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. */ | |
| 3667 | 3692 | Stmt stmt = empty_Stmt; |
| 3668 | 3693 | #define fail(EXPR) blob_appendf EXPR; goto end_footer |
| 3669 | 3694 | |
| 3670 | 3695 | login_check_credentials(); |
| 3671 | 3696 | if( !g.perm.Write ){ |
| 3672 | 3697 | login_needed(g.anon.Write); |
| 3673 | 3698 | return; |
| 3674 | 3699 | } |
| 3700 | + db_begin_transaction(); | |
| 3675 | 3701 | CheckinMiniInfo_init(&cimi); |
| 3676 | 3702 | submitMode = atoi(PD("submit","0")) |
| 3677 | 3703 | /* Submit modes: 0=initial request, |
| 3678 | 3704 | ** 1=submit (save), 2=preview, 3=diff */; |
| 3679 | 3705 | zFlagCheck = P("comment"); |
| @@ -3739,17 +3765,22 @@ | ||
| 3739 | 3765 | |
| 3740 | 3766 | /* All set. Here we go... */ |
| 3741 | 3767 | #define fp fossil_print |
| 3742 | 3768 | /* ^^^ Appologies, Richard, but the @ form plays havoc with emacs */ |
| 3743 | 3769 | |
| 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>"); | |
| 3751 | 3782 | fp("<p>This page is <em>far from complete</em>.</p>\n"); |
| 3752 | 3783 | |
| 3753 | 3784 | fp("<form action='%R/fileedit' method='POST' " |
| 3754 | 3785 | "class='fileedit-form'>\n"); |
| 3755 | 3786 | |
| @@ -3757,66 +3788,69 @@ | ||
| 3757 | 3788 | fp("<input type='hidden' name='r' value='%s'>", zRevResolved); |
| 3758 | 3789 | fp("<input type='hidden' name='file' value='%T'>", |
| 3759 | 3790 | zFilename); |
| 3760 | 3791 | |
| 3761 | 3792 | /******* Comment *******/ |
| 3762 | - fp("<h3>Comment</h3>\n"); | |
| 3793 | + fp("<h3>Checkin Comment</h3>\n"); | |
| 3763 | 3794 | fp("<textarea name='comment' rows='3' cols='80'>"); |
| 3764 | 3795 | if(zComment && *zComment){ |
| 3765 | 3796 | fp("%h"/*%h? %s?*/, zComment); |
| 3766 | 3797 | } |
| 3767 | 3798 | fp("</textarea>\n"); |
| 3768 | 3799 | fp("<div class='hint'>Comments use the Fossil wiki markup " |
| 3769 | 3800 | "syntax.</div>"/*TODO: radiobuttons for fossil/me/plain text*/); |
| 3770 | 3801 | |
| 3771 | 3802 | /******* Content *******/ |
| 3772 | - fp("<h3>Content</h3>\n"); | |
| 3803 | + fp("<h3>File Content</h3>\n"); | |
| 3773 | 3804 | fp("<textarea name='content' id='fileedit-content' " |
| 3774 | 3805 | "rows='20' cols='80'>"); |
| 3775 | 3806 | fp("Loading..."); |
| 3776 | 3807 | fp("</textarea>\n"); |
| 3777 | 3808 | /******* Flags/options *******/ |
| 3778 | 3809 | fp("<fieldset class='fileedit-options'>" |
| 3779 | - "<legend>Many checkboxes are TODO</legend><div>" | |
| 3810 | + "<legend>Options</legend><div>" | |
| 3780 | 3811 | /* Chrome does not sanely lay out multiple |
| 3781 | 3812 | ** fieldset children after the <legend>, so |
| 3782 | 3813 | ** a containing div is necessary. */); |
| 3783 | 3814 | /* |
| 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]. | |
| 3791 | 3817 | */ |
| 3792 | 3818 | if(0==submitMode || P("dry_run")!=0){ |
| 3793 | 3819 | cimi.flags |= CIMINI_DRY_RUN; |
| 3794 | 3820 | } |
| 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); | |
| 3798 | 3824 | if(P("allow_fork")!=0){ |
| 3799 | 3825 | cimi.flags |= CIMINI_ALLOW_FORK; |
| 3800 | 3826 | } |
| 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); | |
| 3804 | 3837 | if(P("exec_bit")!=0){ |
| 3805 | 3838 | cimi.filePerm = PERM_EXE; |
| 3806 | 3839 | } |
| 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); | |
| 3810 | 3843 | if(P("allow_merge_conflict")!=0){ |
| 3811 | 3844 | cimi.flags |= CIMINI_ALLOW_MERGE_MARKER; |
| 3812 | 3845 | } |
| 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); | |
| 3818 | 3852 | {/* EOL conversion policy... */ |
| 3819 | 3853 | const int eolMode = submitMode==0 ? 0 : atoi(PD("eol","0")); |
| 3820 | 3854 | switch(eolMode){ |
| 3821 | 3855 | case 1: cimi.flags |= CIMINI_CONVERT_EOL_UNIX; break; |
| 3822 | 3856 | case 2: cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; break; |
| @@ -3824,17 +3858,27 @@ | ||
| 3824 | 3858 | } |
| 3825 | 3859 | fp("<select name='eol' " |
| 3826 | 3860 | "title='EOL conversion policy, noting that form-processing " |
| 3827 | 3861 | "may implicitly change the line endings of the input.'>"); |
| 3828 | 3862 | fp("<option value='0'%s>Inherit EOLs</option>", |
| 3829 | - eolMode==0 ? " selected" : ""); | |
| 3863 | + (eolMode!=1 && eolMode!=2) ? " selected" : ""); | |
| 3830 | 3864 | fp("<option value='1'%s/>Unix EOLs</option>", |
| 3831 | 3865 | eolMode==1 ? " selected" : ""); |
| 3832 | 3866 | fp("<option value='2'%s>Windows EOLs</option>", |
| 3833 | 3867 | eolMode==2 ? " selected" : ""); |
| 3834 | 3868 | fp("</select>"); |
| 3835 | 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 | + | |
| 3836 | 3880 | fp("</div></fieldset>") /* end of checkboxes */; |
| 3837 | 3881 | |
| 3838 | 3882 | /******* Buttons *******/ |
| 3839 | 3883 | fp("<fieldset class='fileedit-options'>" |
| 3840 | 3884 | "<legend>Several buttons are TODO</legend><div>"); |
| @@ -3847,68 +3891,89 @@ | ||
| 3847 | 3891 | fp("</div></fieldset>"); |
| 3848 | 3892 | |
| 3849 | 3893 | /******* End of form *******/ |
| 3850 | 3894 | fp("</form>\n"); |
| 3851 | 3895 | |
| 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 | + */ | |
| 3864 | 3908 | char const * zQuoted = 0; |
| 3865 | 3909 | 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); | |
| 3867 | 3911 | db_step(&stmt); |
| 3868 | 3912 | zQuoted = db_column_text(&stmt,0); |
| 3869 | 3913 | } |
| 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"); | |
| 3872 | 3918 | if(stmt.pStmt){ |
| 3873 | 3919 | db_finalize(&stmt); |
| 3874 | 3920 | } |
| 3875 | 3921 | } |
| 3876 | - fileedit_emit_script(1); | |
| 3877 | - | |
| 3878 | - zContent = 0; | |
| 3879 | - fossil_free(zRevResolved); | |
| 3880 | - fossil_free(zFileUuid); | |
| 3881 | 3922 | |
| 3882 | 3923 | if(1==submitMode/*save*/){ |
| 3883 | 3924 | 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; | |
| 3888 | 3926 | /*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 | + } | |
| 3891 | 3932 | cimi.pParent = manifest_get(vid, CFTYPE_MANIFEST, 0); |
| 3892 | 3933 | assert(cimi.pParent && "We know vid is valid."); |
| 3893 | 3934 | cimi.zFilename = mprintf("%s",zFilename); |
| 3894 | 3935 | 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); | |
| 3899 | 3937 | if(newVid!=0){ |
| 3900 | - char * zNewUuid = rid_to_uuid(newVid); | |
| 3938 | + zNewUuid = rid_to_uuid(newVid); | |
| 3901 | 3939 | fp("<h3>Manifest%s: %S</h3><pre>" |
| 3902 | 3940 | "<code class='fileedit-manifest'>%h</code>" |
| 3903 | 3941 | "</pre>", |
| 3904 | 3942 | (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", |
| 3905 | 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 | + } | |
| 3906 | 3970 | fossil_free(zNewUuid); |
| 3907 | 3971 | } |
| 3972 | + /* On error, the error message is in the err blob and will | |
| 3973 | + ** be emitted below. */ | |
| 3908 | 3974 | blob_reset(&manifest); |
| 3909 | - db_end_transaction(rc ? 0 : 1); | |
| 3910 | 3975 | }else if(2==submitMode/*preview*/){ |
| 3911 | 3976 | /* TODO */ |
| 3912 | 3977 | fail((&err,"Preview mode is still TODO.")); |
| 3913 | 3978 | }else if(3==submitMode/*diff*/){ |
| 3914 | 3979 | fail((&err,"Diff mode is still TODO.")); |
| @@ -3916,17 +3981,30 @@ | ||
| 3916 | 3981 | /* Ignore invalid submitMode value */ |
| 3917 | 3982 | goto end_footer; |
| 3918 | 3983 | } |
| 3919 | 3984 | |
| 3920 | 3985 | end_footer: |
| 3986 | + zContent = 0; | |
| 3987 | + fossil_free(zRevResolved); | |
| 3988 | + fossil_free(zFileUuid); | |
| 3921 | 3989 | if(stmt.pStmt){ |
| 3922 | 3990 | db_finalize(&stmt); |
| 3923 | 3991 | } |
| 3924 | 3992 | if(blob_size(&err)){ |
| 3925 | 3993 | fp("<div class='fileedit-error-report'>%h</div>", |
| 3926 | 3994 | blob_str(&err)); |
| 3927 | 3995 | } |
| 3928 | 3996 | blob_reset(&err); |
| 3929 | 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. */); | |
| 3930 | 4008 | style_footer(); |
| 3931 | 4009 | #undef fp |
| 3932 | 4010 | } |
| 3933 | 4011 |
| --- 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 |
+1
| --- src/default_css.txt | ||
| +++ src/default_css.txt | ||
| @@ -898,10 +898,11 @@ | ||
| 898 | 898 | border: 1px inset #808080; |
| 899 | 899 | border-radius: 0.5em; |
| 900 | 900 | padding: 0.25em 0.4em; |
| 901 | 901 | margin: 0 0.5em; |
| 902 | 902 | display: inline-block; |
| 903 | + cursor: pointer; | |
| 903 | 904 | } |
| 904 | 905 | .input-with-label > input { |
| 905 | 906 | margin: 0; |
| 906 | 907 | } |
| 907 | 908 | .input-with-label > input[type=checkbox] { |
| 908 | 909 |
| --- 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 |