Fossil SCM

The new skin editing is working, minimally. Still needs lots of work, though.

drh 2017-12-02 21:24 UTC skin-setup-refactor
Commit 5840fdd73299f9da71f8c9b18720ca5b3a58bd52574e08ae5dd51b5830d8d0ac
1 file changed +178 -40
+178 -40
--- src/skins.c
+++ src/skins.c
@@ -53,10 +53,15 @@
5353
{ "Black & White, Menu on Left", "black_and_white", 0 },
5454
{ "Plain Gray, No Logo", "plain_gray", 0 },
5555
{ "Khaki, No Logo", "khaki", 0 },
5656
};
5757
58
+/*
59
+** A skin consists of four "files" named here:
60
+*/
61
+static const char *azSkinFile[] = { "css", "header", "footer", "details" };
62
+
5863
/*
5964
** Alternative skins can be specified in the CGI script or by options
6065
** on the "http", "ui", and "server" commands. The alternative skin
6166
** name must be one of the aBuiltinSkin[].zLabel names. If there is
6267
** a match, that alternative is used.
@@ -331,30 +336,29 @@
331336
** Memory to hold the returned string is obtained from malloc.
332337
*/
333338
static char *getSkin(const char *zName){
334339
const char *z;
335340
char *zLabel;
336
- static const char *azType[] = { "css", "header", "footer", "details" };
337341
int i;
338342
Blob val;
339343
blob_zero(&val);
340
- for(i=0; i<count(azType); i++){
344
+ for(i=0; i<count(azSkinFile); i++){
341345
if( zName ){
342
- zLabel = mprintf("skins/%s/%s.txt", zName, azType[i]);
346
+ zLabel = mprintf("skins/%s/%s.txt", zName, azSkinFile[i]);
343347
z = builtin_text(zLabel);
344348
fossil_free(zLabel);
345349
}else{
346
- z = db_get(azType[i], 0);
350
+ z = db_get(azSkinFile[i], 0);
347351
if( z==0 ){
348
- zLabel = mprintf("skins/default/%s.txt", azType[i]);
352
+ zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]);
349353
z = builtin_text(zLabel);
350354
fossil_free(zLabel);
351355
}
352356
}
353357
blob_appendf(&val,
354358
"REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
355
- azType[i], z
359
+ azSkinFile[i], z
356360
);
357361
}
358362
return blob_str(&val);
359363
}
360364
@@ -440,11 +444,11 @@
440444
441445
/*
442446
** WEBPAGE: setup_skin_old
443447
**
444448
** Show a list of available skins with buttons for selecting which
445
-** skin to use. Requires Admin privilege.
449
+** skin to use. Requires Setup privilege.
446450
*/
447451
void setup_skin_old(void){
448452
const char *z;
449453
char *zName;
450454
char *zErr = 0;
@@ -601,13 +605,14 @@
601605
602606
/*
603607
** WEBPAGE: setup_skinedit
604608
**
605609
** Edit aspects of a skin determined by the w= query parameter.
606
-** Requires Admin privileges.
610
+** Requires Setup privileges.
607611
**
608
-** w=N -- 0=CSS, 1=footer, 2=header, 3=details
612
+** w=NUM -- 0=CSS, 1=footer, 2=header, 3=details
613
+** sk=NUM -- the draft skin number
609614
*/
610615
void setup_skinedit(void){
611616
static const struct sSkinAddr {
612617
const char *zFile;
613618
const char *zTitle;
@@ -619,20 +624,40 @@
619624
/* 3 */ { "details", "Display Details", "Details", },
620625
};
621626
const char *zBasis;
622627
const char *zContent;
623628
char *zDflt;
629
+ char *zKey;
630
+ int iSkin;
624631
int ii;
625632
int j;
626633
627634
login_check_credentials();
635
+
636
+ /* Figure out which skin we are editing */
637
+ iSkin = atoi(PD("sk","1"));
638
+ if( iSkin<1 || iSkin>9 ) iSkin = 1;
639
+
640
+ /* Check that the user is authorized to edit this skin. */
628641
if( !g.perm.Setup ){
629
- login_needed(0);
630
- return;
642
+ char *zAllowedEditors = db_get_mprintf("draft%d-users", "", iSkin);
643
+ Glob *pAllowedEditors;
644
+ if( zAllowedEditors[0] ){
645
+ pAllowedEditors = glob_create(zAllowedEditors);
646
+ if( !glob_match(pAllowedEditors, zAllowedEditors) ){
647
+ login_needed(0);
648
+ return;
649
+ }
650
+ glob_free(pAllowedEditors);
651
+ }
631652
}
653
+
654
+ /* figure out which file is to be edited */
632655
ii = atoi(PD("w","0"));
633656
if( ii<0 || ii>count(aSkinAttr) ) ii = 0;
657
+ zKey = mprintf("draft%d-%s", iSkin, aSkinAttr[ii].zFile);
658
+
634659
zBasis = PD("basis","default");
635660
zDflt = mprintf("skins/%s/%s.txt", zBasis, aSkinAttr[ii].zFile);
636661
db_begin_transaction();
637662
if( P("revert")!=0 ){
638663
db_multi_exec("DELETE FROM config WHERE name=%Q", aSkinAttr[ii].zFile);
@@ -642,16 +667,16 @@
642667
for(j=0; j<count(aSkinAttr); j++){
643668
if( j==ii ) continue;
644669
style_submenu_element(aSkinAttr[j].zSubmenu,
645670
"%R/setup_skinedit?w=%d&basis=%h",j,zBasis);
646671
}
647
- style_submenu_element("Skins", "%R/setup_skin_old");
648672
@ <form action="%s(g.zTop)/setup_skinedit" method="post"><div>
649673
login_insert_csrf_secret();
650674
@ <input type='hidden' name='w' value='%d(ii)'>
675
+ @ <input type='hidden' name='sk' value='%d(iSkin)'>
651676
@ <h2>Edit %s(aSkinAttr[ii].zTitle):</h2>
652
- zContent = textarea_attribute("", 10, 80, aSkinAttr[ii].zFile,
677
+ zContent = textarea_attribute("", 10, 80, zKey,
653678
aSkinAttr[ii].zFile, builtin_text(zDflt), 0);
654679
@ <br />
655680
@ <input type="submit" name="submit" value="Apply Changes" />
656681
@ <hr />
657682
@ Baseline: <select size='1' name='basis'>
@@ -695,39 +720,83 @@
695720
** Try to initialize draft skin iSkin to the built-in or preexisting
696721
** skin named by zTemplate.
697722
*/
698723
static void skin_initialize_draft(int iSkin, const char *zTemplate){
699724
int i;
700
- const char *azWhat[] = { "css", "header", "footer", "detail" };
701725
if( zTemplate==0 ) return;
702726
if( strcmp(zTemplate, "current")==0 ){
703
- for(i=0; i<count(azWhat); i++){
704
- db_unset_mprintf("draft%d-%s", 0, iSkin, azWhat[i]);
727
+ for(i=0; i<count(azSkinFile); i++){
728
+ db_unset_mprintf("draft%d-%s", 0, iSkin, azSkinFile[i]);
705729
}
706730
}else{
707731
for(i=0; i<count(aBuiltinSkin); i++){
708732
if( strcmp(zTemplate, aBuiltinSkin[i].zLabel)==0 ){
709
- for(i=0; i<count(azWhat); i++){
710
- char *zKey = mprintf("skins/%s/%s.txt", zTemplate, azWhat[i]);
711
- db_set_mprintf("draft%d-%s", builtin_text(zKey), 0, iSkin, azWhat[i]);
733
+ for(i=0; i<count(azSkinFile); i++){
734
+ char *zKey = mprintf("skins/%s/%s.txt", zTemplate, azSkinFile[i]);
735
+ db_set_mprintf("draft%d-%s", builtin_text(zKey), 0,
736
+ iSkin, azSkinFile[i]);
712737
}
713738
break;
714739
}
715740
}
716741
}
717742
}
743
+
744
+/*
745
+** Publish the draft skin iSkin as the new default.
746
+*/
747
+static void skin_publish(int iSkin){
748
+ char *zCurrent; /* SQL description of the current skin */
749
+ char *zBuiltin; /* SQL description of a built-in skin */
750
+ int i;
751
+ int seen = 0; /* True if no need to make a backup */
752
+
753
+ /* Check to see if the current skin is already saved. If it is, there
754
+ ** is no need to create a backup */
755
+ zCurrent = getSkin(0);
756
+ for(i=0; i<count(aBuiltinSkin); i++){
757
+ zBuiltin = getSkin(aBuiltinSkin[i].zLabel);
758
+ if( fossil_strcmp(aBuiltinSkin[i].zSQL, zCurrent)==0 ){
759
+ seen = 1;
760
+ break;
761
+ }
762
+ }
763
+ if( !seen ){
764
+ seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
765
+ " AND value=%Q", zCurrent);
766
+ }
767
+ if( !seen ){
768
+ db_multi_exec(
769
+ "INSERT INTO config(name,value,mtime) VALUES("
770
+ " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
771
+ " %Q,now())", zCurrent
772
+ );
773
+ }
774
+
775
+ /* Publish draft iSkin */
776
+ for(i=0; i<count(azSkinFile); i++){
777
+ db_multi_exec(
778
+ "UPDATE config"
779
+ " SET value=(SELECT value FROM config AS x"
780
+ " WHERE x.name = printf('draft%d-%%s',config.name)),"
781
+ " mtime=now()"
782
+ " WHERE name IN ('css','header','footer','details')", iSkin
783
+ );
784
+ }
785
+}
718786
719787
/*
720788
** WEBPAGE: setup_skin
721789
**
722790
** Generate a page showing the steps needed to customize a skin.
723791
*/
724792
void setup_skin(void){
725793
int i; /* Loop counter */
726794
int iSkin; /* Which draft skin is being edited */
727
- int isAdmin; /* True for an administrator */
795
+ int isSetup; /* True for an administrator */
728796
int isEditor; /* Others authorized to make edits */
797
+ char *zAllowedEditors; /* Who may edit the draft skin */
729798
static const char *azTestPages[] = {
730799
"home",
731800
"timeline",
732801
"dir?ci=tip",
733802
"dir?ci=tip&type=tree",
@@ -741,27 +810,34 @@
741810
742811
/* Figure out if the current user is allowed to make administrative
743812
** changes and/or edits
744813
*/
745814
login_check_credentials();
746
- if( g.perm.Admin ){
747
- isAdmin = isEditor = 1;
815
+ zAllowedEditors = db_get_mprintf("draft%d-users", "", iSkin);
816
+ if( g.perm.Setup ){
817
+ isSetup = isEditor = 1;
748818
}else{
749
- char *zAllowedEditors;
750819
Glob *pAllowedEditors;
751
- isAdmin = isEditor = 0;
752
- zAllowedEditors = db_get_mprintf("draft%d-users", 0, iSkin);
753
- if( zAllowedEditors ){
820
+ isSetup = isEditor = 0;
821
+ if( zAllowedEditors[0] ){
754822
pAllowedEditors = glob_create(zAllowedEditors);
755823
isEditor = glob_match(pAllowedEditors, zAllowedEditors);
756824
glob_free(pAllowedEditors);
757825
}
758826
}
759827
760828
/* Initialize the skin, if requested and authorized. */
761829
if( P("init3")!=0 && isEditor ){
762830
skin_initialize_draft(iSkin, P("initskin"));
831
+ }
832
+ if( P("e3")!=0 && isSetup ){
833
+ db_set_mprintf("draft%d-users", PD("editors",""), 0, iSkin);
834
+ }
835
+
836
+ /* Publish the draft skin */
837
+ if( P("pub7")!=0 && PB("pub7ck1") && PB("pub7ck2") ){
838
+ skin_publish(iSkin);
763839
}
764840
765841
style_header("Customize Skin");
766842
767843
@ <p>Customize the look of this Fossil repository by making changes
@@ -776,11 +852,11 @@
776852
@ edits are made to one of nine draft skins. A draft skin can then
777853
@ be published to become the default skin.
778854
@ Nine separate drafts are available to facilitate A/B testing.</p>
779855
@
780856
@ <form method='POST' action='%R/setup_skin#step2' id='f01'>
781
- @ <p>Skin to edit:
857
+ @ <p class='skinInput'>Draft skin to edit:
782858
@ <select size='1' name='sk' onchange='gebi("f01").submit()'>
783859
for(i=1; i<=9; i++){
784860
if( i==iSkin ){
785861
@ <option value='%d(i)' selected>draft%d(i)</option>
786862
}else{
@@ -789,11 +865,36 @@
789865
}
790866
@ </select>
791867
@ </p>
792868
@
793869
@ <a name='step2'></a>
794
- @ <h1>Step 2: Authenticate
870
+ @ <h1>Step 2: Authenticate</h1>
871
+ @
872
+ if( isSetup ){
873
+ @ <p>As an administrator, you can make any edits you like to this or
874
+ @ any other skin. You can also authorized other users to edit this
875
+ @ skin. Any user whose login name matches the comma-separate list
876
+ @ of GLOB expressions below is given special permission to edit
877
+ @ the draft%d(iSkin) skin:
878
+ @
879
+ @ <form method='POST' action='%R/setup_skin#step2' id='f02'>
880
+ @ <p class='skinInput'>
881
+ @ <input type='hidden' name='sk' value='%d(iSkin)'>
882
+ @ Authorized editors for skin draft%d(iSkin):
883
+ @ <input type='text' name='editors' value='%h(zAllowedEditors)'\
884
+ @ width='40'>
885
+ @ <input type='submit' name='submit2' value='Change'>
886
+ @ </p>
887
+ @ </form>
888
+ }else if( isEditor ){
889
+ @ <p>You are authorized to make changes to the draft%d(iSkin) skin.
890
+ @ Continue to the <a href='#step3'>next step</a>.</p>
891
+ }else{
892
+ @ <p>You are not authorized to make changes to the draft%d(iSkin)
893
+ @ skin. Contact the administrator of this Fossil repository for
894
+ @ further information.</p>
895
+ }
795896
@
796897
@ <a name='step3'></a>
797898
@ <h1>Step 3: Initialize The Draft</h1>
798899
@
799900
if( !isEditor ){
@@ -801,51 +902,88 @@
801902
@ the administrator for this repository for more information.
802903
}else{
803904
@ <p>Initialize the draft%d(iSkin) skin to one of the built-in skins
804905
@ or a preexisting skin, to use as a baseline.</p>
805906
@
806
- @ <p><form method='POST' action='%R/setup_skin#stop4' id='f03'>
907
+ @ <form method='POST' action='%R/setup_skin#step4' id='f03'>
908
+ @ <p class='skinInput'>
807909
@ <input type='hidden' name='sk' value='%d(iSkin)'>
808
- @ Initialize <b>draft%d(iSkin)</b> to
910
+ @ Initialize skin <b>draft%d(iSkin)</b> using
809911
@ <select size='1' name='initskin'>
810912
@ <option value='current'>Currently In Use</option>
811913
for(i=0; i<count(aBuiltinSkin); i++){
812914
@ <option value='%s(aBuiltinSkin[i].zLabel)'>\
813915
@ %h(aBuiltinSkin[i].zDesc) (built-in)</option>
814916
}
815917
@ </select>
816918
@ <input type='submit' name='init3' value='Go'>
817919
@ </p>
920
+ @ </form>
818921
}
819922
@
820923
@ <a name='step4'></a>
821924
@ <h1>Step 4: Make Edits</h1>
925
+ @
926
+ if( !isEditor ){
927
+ @ <p>You are not authorized to make edits to the draft%d(iSkin) skin.
928
+ @ Contact the administrator of this Fossil repository for help.</p>
929
+ }else{
930
+ @ <p>Edit the components of the draft%d(iSkin) skin:
931
+ @ <ul>
932
+ @ <li><a href='%R/setup_skinedit?w=0&sk=%d(iSkin)' target='_blank'>CSS</a>
933
+ @ <li><a href='%R/setup_skinedit?w=1&sk=%d(iSkin)' target='_blank'>\
934
+ @ Header</a>
935
+ @ <li><a href='%R/setup_skinedit?w=2&sk=%d(iSkin)' target='_blank'>\
936
+ @ Footer</a>
937
+ @ <li><a href='%R/setup_skinedit?w=3&sk=%d(iSkin)' target='_blank'>\
938
+ @ Details</a>
939
+ @ </ul>
940
+ }
822941
@
823942
@ <a name='step5'></a>
824943
@ <h1>Step 5: Verify The Draft Skin</h1>
825944
@
826945
@ <p>To test this draft skin, insert text "/draft%d(iSkin)/" just before the
827946
@ operation name in the URL. Here are a few links to try:
828947
@ <ul>
829948
for(i=0; i<count(azTestPages); i++){
830
- @ <li><a href='%s(g.zBaseURL)/draft%d(iSkin)/%s(azTestPages[i])'>\
831
- @ %s(g.zBaseURL)/draft%d(iSkin)/%s(azTestPages[i])</a>
949
+ @ <li><a href='%s(g.zBaseURL)/draft%d(iSkin)/%s(azTestPages[i])' \
950
+ @ target='_blank'>%s(g.zBaseURL)/draft%d(iSkin)/%s(azTestPages[i])</a>
832951
}
833952
@ </ul>
834953
@
835
- @ <p><b>Important:</b> After CSS changes, you will probably need to
836
- @ press the "Reload" button on your browser for those changes
837
- @ to take effect.</p>
954
+ @ <p>You will probably need to press Reload on your browser before any
955
+ @ CSS changes will take effect.</p>
956
+ @
957
+ @ <a hame='step6'></a>
958
+ @ <h1>Step 6: Interate</h1>
959
+ @
960
+ @ <p>Repeat <a href='#step4'>step 4</a> and
961
+ @ <a href='#step5'>step 5</a> as many times as necessary to create
962
+ @ a production-ready skin.
838963
@
839964
@ <a name='step6'></a>
840
- @ <h1>Step 6: Publish The Draft</h1>
965
+ @ <h1>Step 7: Publish The Draft</h1>
966
+ @
841967
if( !g.perm.Setup ){
842968
@ <p>Only administrators are allowed to publish draft skins. Contact
843969
@ an administrator to get this "draft%d(iSkin)" skin published.</p>
844970
}else{
845
-
971
+ @ <p>When the draft%d(iSkin) skin is ready for production use,
972
+ @ make it the default scan by clicking the acknowledgements and
973
+ @ pressing the button below:</p>
974
+ @
975
+ @ <form method='POST' action='%R/setup_skin#step7'>
976
+ @ <p class='skinInput'>
977
+ @ <input type='hidden' name='sk' value='%d(iSkin)'>
978
+ @ <input type='checkbox' name='pub7ck1' value='yes'>\
979
+ @ Skin draft%d(iSkin) has been testing and found ready for production.<br>
980
+ @ <input type='checkbox' name='pub7ck2' value='yes'>\
981
+ @ The current skin should be overwritten with draft%d(iSkin).<br>
982
+ @ <input type='submit' name='pub7' value='Publish Draft%d(iSkin)'>
983
+ @ </p></form>
984
+ @
985
+ @ <p>You will probably need to press Reload on your browser after
986
+ @ publishing the new skin.</p>
846987
}
847
- @
848
- @ <a name='step7'></a>
849
- @ <h1>Step 7: Cleanup</h1>
850988
style_footer();
851989
}
852990
--- src/skins.c
+++ src/skins.c
@@ -53,10 +53,15 @@
53 { "Black & White, Menu on Left", "black_and_white", 0 },
54 { "Plain Gray, No Logo", "plain_gray", 0 },
55 { "Khaki, No Logo", "khaki", 0 },
56 };
57
 
 
 
 
 
58 /*
59 ** Alternative skins can be specified in the CGI script or by options
60 ** on the "http", "ui", and "server" commands. The alternative skin
61 ** name must be one of the aBuiltinSkin[].zLabel names. If there is
62 ** a match, that alternative is used.
@@ -331,30 +336,29 @@
331 ** Memory to hold the returned string is obtained from malloc.
332 */
333 static char *getSkin(const char *zName){
334 const char *z;
335 char *zLabel;
336 static const char *azType[] = { "css", "header", "footer", "details" };
337 int i;
338 Blob val;
339 blob_zero(&val);
340 for(i=0; i<count(azType); i++){
341 if( zName ){
342 zLabel = mprintf("skins/%s/%s.txt", zName, azType[i]);
343 z = builtin_text(zLabel);
344 fossil_free(zLabel);
345 }else{
346 z = db_get(azType[i], 0);
347 if( z==0 ){
348 zLabel = mprintf("skins/default/%s.txt", azType[i]);
349 z = builtin_text(zLabel);
350 fossil_free(zLabel);
351 }
352 }
353 blob_appendf(&val,
354 "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
355 azType[i], z
356 );
357 }
358 return blob_str(&val);
359 }
360
@@ -440,11 +444,11 @@
440
441 /*
442 ** WEBPAGE: setup_skin_old
443 **
444 ** Show a list of available skins with buttons for selecting which
445 ** skin to use. Requires Admin privilege.
446 */
447 void setup_skin_old(void){
448 const char *z;
449 char *zName;
450 char *zErr = 0;
@@ -601,13 +605,14 @@
601
602 /*
603 ** WEBPAGE: setup_skinedit
604 **
605 ** Edit aspects of a skin determined by the w= query parameter.
606 ** Requires Admin privileges.
607 **
608 ** w=N -- 0=CSS, 1=footer, 2=header, 3=details
 
609 */
610 void setup_skinedit(void){
611 static const struct sSkinAddr {
612 const char *zFile;
613 const char *zTitle;
@@ -619,20 +624,40 @@
619 /* 3 */ { "details", "Display Details", "Details", },
620 };
621 const char *zBasis;
622 const char *zContent;
623 char *zDflt;
 
 
624 int ii;
625 int j;
626
627 login_check_credentials();
 
 
 
 
 
 
628 if( !g.perm.Setup ){
629 login_needed(0);
630 return;
 
 
 
 
 
 
 
 
631 }
 
 
632 ii = atoi(PD("w","0"));
633 if( ii<0 || ii>count(aSkinAttr) ) ii = 0;
 
 
634 zBasis = PD("basis","default");
635 zDflt = mprintf("skins/%s/%s.txt", zBasis, aSkinAttr[ii].zFile);
636 db_begin_transaction();
637 if( P("revert")!=0 ){
638 db_multi_exec("DELETE FROM config WHERE name=%Q", aSkinAttr[ii].zFile);
@@ -642,16 +667,16 @@
642 for(j=0; j<count(aSkinAttr); j++){
643 if( j==ii ) continue;
644 style_submenu_element(aSkinAttr[j].zSubmenu,
645 "%R/setup_skinedit?w=%d&basis=%h",j,zBasis);
646 }
647 style_submenu_element("Skins", "%R/setup_skin_old");
648 @ <form action="%s(g.zTop)/setup_skinedit" method="post"><div>
649 login_insert_csrf_secret();
650 @ <input type='hidden' name='w' value='%d(ii)'>
 
651 @ <h2>Edit %s(aSkinAttr[ii].zTitle):</h2>
652 zContent = textarea_attribute("", 10, 80, aSkinAttr[ii].zFile,
653 aSkinAttr[ii].zFile, builtin_text(zDflt), 0);
654 @ <br />
655 @ <input type="submit" name="submit" value="Apply Changes" />
656 @ <hr />
657 @ Baseline: <select size='1' name='basis'>
@@ -695,39 +720,83 @@
695 ** Try to initialize draft skin iSkin to the built-in or preexisting
696 ** skin named by zTemplate.
697 */
698 static void skin_initialize_draft(int iSkin, const char *zTemplate){
699 int i;
700 const char *azWhat[] = { "css", "header", "footer", "detail" };
701 if( zTemplate==0 ) return;
702 if( strcmp(zTemplate, "current")==0 ){
703 for(i=0; i<count(azWhat); i++){
704 db_unset_mprintf("draft%d-%s", 0, iSkin, azWhat[i]);
705 }
706 }else{
707 for(i=0; i<count(aBuiltinSkin); i++){
708 if( strcmp(zTemplate, aBuiltinSkin[i].zLabel)==0 ){
709 for(i=0; i<count(azWhat); i++){
710 char *zKey = mprintf("skins/%s/%s.txt", zTemplate, azWhat[i]);
711 db_set_mprintf("draft%d-%s", builtin_text(zKey), 0, iSkin, azWhat[i]);
 
712 }
713 break;
714 }
715 }
716 }
717 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718
719 /*
720 ** WEBPAGE: setup_skin
721 **
722 ** Generate a page showing the steps needed to customize a skin.
723 */
724 void setup_skin(void){
725 int i; /* Loop counter */
726 int iSkin; /* Which draft skin is being edited */
727 int isAdmin; /* True for an administrator */
728 int isEditor; /* Others authorized to make edits */
 
729 static const char *azTestPages[] = {
730 "home",
731 "timeline",
732 "dir?ci=tip",
733 "dir?ci=tip&type=tree",
@@ -741,27 +810,34 @@
741
742 /* Figure out if the current user is allowed to make administrative
743 ** changes and/or edits
744 */
745 login_check_credentials();
746 if( g.perm.Admin ){
747 isAdmin = isEditor = 1;
 
748 }else{
749 char *zAllowedEditors;
750 Glob *pAllowedEditors;
751 isAdmin = isEditor = 0;
752 zAllowedEditors = db_get_mprintf("draft%d-users", 0, iSkin);
753 if( zAllowedEditors ){
754 pAllowedEditors = glob_create(zAllowedEditors);
755 isEditor = glob_match(pAllowedEditors, zAllowedEditors);
756 glob_free(pAllowedEditors);
757 }
758 }
759
760 /* Initialize the skin, if requested and authorized. */
761 if( P("init3")!=0 && isEditor ){
762 skin_initialize_draft(iSkin, P("initskin"));
 
 
 
 
 
 
 
 
763 }
764
765 style_header("Customize Skin");
766
767 @ <p>Customize the look of this Fossil repository by making changes
@@ -776,11 +852,11 @@
776 @ edits are made to one of nine draft skins. A draft skin can then
777 @ be published to become the default skin.
778 @ Nine separate drafts are available to facilitate A/B testing.</p>
779 @
780 @ <form method='POST' action='%R/setup_skin#step2' id='f01'>
781 @ <p>Skin to edit:
782 @ <select size='1' name='sk' onchange='gebi("f01").submit()'>
783 for(i=1; i<=9; i++){
784 if( i==iSkin ){
785 @ <option value='%d(i)' selected>draft%d(i)</option>
786 }else{
@@ -789,11 +865,36 @@
789 }
790 @ </select>
791 @ </p>
792 @
793 @ <a name='step2'></a>
794 @ <h1>Step 2: Authenticate
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795 @
796 @ <a name='step3'></a>
797 @ <h1>Step 3: Initialize The Draft</h1>
798 @
799 if( !isEditor ){
@@ -801,51 +902,88 @@
801 @ the administrator for this repository for more information.
802 }else{
803 @ <p>Initialize the draft%d(iSkin) skin to one of the built-in skins
804 @ or a preexisting skin, to use as a baseline.</p>
805 @
806 @ <p><form method='POST' action='%R/setup_skin#stop4' id='f03'>
 
807 @ <input type='hidden' name='sk' value='%d(iSkin)'>
808 @ Initialize <b>draft%d(iSkin)</b> to
809 @ <select size='1' name='initskin'>
810 @ <option value='current'>Currently In Use</option>
811 for(i=0; i<count(aBuiltinSkin); i++){
812 @ <option value='%s(aBuiltinSkin[i].zLabel)'>\
813 @ %h(aBuiltinSkin[i].zDesc) (built-in)</option>
814 }
815 @ </select>
816 @ <input type='submit' name='init3' value='Go'>
817 @ </p>
 
818 }
819 @
820 @ <a name='step4'></a>
821 @ <h1>Step 4: Make Edits</h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822 @
823 @ <a name='step5'></a>
824 @ <h1>Step 5: Verify The Draft Skin</h1>
825 @
826 @ <p>To test this draft skin, insert text "/draft%d(iSkin)/" just before the
827 @ operation name in the URL. Here are a few links to try:
828 @ <ul>
829 for(i=0; i<count(azTestPages); i++){
830 @ <li><a href='%s(g.zBaseURL)/draft%d(iSkin)/%s(azTestPages[i])'>\
831 @ %s(g.zBaseURL)/draft%d(iSkin)/%s(azTestPages[i])</a>
832 }
833 @ </ul>
834 @
835 @ <p><b>Important:</b> After CSS changes, you will probably need to
836 @ press the "Reload" button on your browser for those changes
837 @ to take effect.</p>
 
 
 
 
 
 
838 @
839 @ <a name='step6'></a>
840 @ <h1>Step 6: Publish The Draft</h1>
 
841 if( !g.perm.Setup ){
842 @ <p>Only administrators are allowed to publish draft skins. Contact
843 @ an administrator to get this "draft%d(iSkin)" skin published.</p>
844 }else{
845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846 }
847 @
848 @ <a name='step7'></a>
849 @ <h1>Step 7: Cleanup</h1>
850 style_footer();
851 }
852
--- src/skins.c
+++ src/skins.c
@@ -53,10 +53,15 @@
53 { "Black & White, Menu on Left", "black_and_white", 0 },
54 { "Plain Gray, No Logo", "plain_gray", 0 },
55 { "Khaki, No Logo", "khaki", 0 },
56 };
57
58 /*
59 ** A skin consists of four "files" named here:
60 */
61 static const char *azSkinFile[] = { "css", "header", "footer", "details" };
62
63 /*
64 ** Alternative skins can be specified in the CGI script or by options
65 ** on the "http", "ui", and "server" commands. The alternative skin
66 ** name must be one of the aBuiltinSkin[].zLabel names. If there is
67 ** a match, that alternative is used.
@@ -331,30 +336,29 @@
336 ** Memory to hold the returned string is obtained from malloc.
337 */
338 static char *getSkin(const char *zName){
339 const char *z;
340 char *zLabel;
 
341 int i;
342 Blob val;
343 blob_zero(&val);
344 for(i=0; i<count(azSkinFile); i++){
345 if( zName ){
346 zLabel = mprintf("skins/%s/%s.txt", zName, azSkinFile[i]);
347 z = builtin_text(zLabel);
348 fossil_free(zLabel);
349 }else{
350 z = db_get(azSkinFile[i], 0);
351 if( z==0 ){
352 zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]);
353 z = builtin_text(zLabel);
354 fossil_free(zLabel);
355 }
356 }
357 blob_appendf(&val,
358 "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
359 azSkinFile[i], z
360 );
361 }
362 return blob_str(&val);
363 }
364
@@ -440,11 +444,11 @@
444
445 /*
446 ** WEBPAGE: setup_skin_old
447 **
448 ** Show a list of available skins with buttons for selecting which
449 ** skin to use. Requires Setup privilege.
450 */
451 void setup_skin_old(void){
452 const char *z;
453 char *zName;
454 char *zErr = 0;
@@ -601,13 +605,14 @@
605
606 /*
607 ** WEBPAGE: setup_skinedit
608 **
609 ** Edit aspects of a skin determined by the w= query parameter.
610 ** Requires Setup privileges.
611 **
612 ** w=NUM -- 0=CSS, 1=footer, 2=header, 3=details
613 ** sk=NUM -- the draft skin number
614 */
615 void setup_skinedit(void){
616 static const struct sSkinAddr {
617 const char *zFile;
618 const char *zTitle;
@@ -619,20 +624,40 @@
624 /* 3 */ { "details", "Display Details", "Details", },
625 };
626 const char *zBasis;
627 const char *zContent;
628 char *zDflt;
629 char *zKey;
630 int iSkin;
631 int ii;
632 int j;
633
634 login_check_credentials();
635
636 /* Figure out which skin we are editing */
637 iSkin = atoi(PD("sk","1"));
638 if( iSkin<1 || iSkin>9 ) iSkin = 1;
639
640 /* Check that the user is authorized to edit this skin. */
641 if( !g.perm.Setup ){
642 char *zAllowedEditors = db_get_mprintf("draft%d-users", "", iSkin);
643 Glob *pAllowedEditors;
644 if( zAllowedEditors[0] ){
645 pAllowedEditors = glob_create(zAllowedEditors);
646 if( !glob_match(pAllowedEditors, zAllowedEditors) ){
647 login_needed(0);
648 return;
649 }
650 glob_free(pAllowedEditors);
651 }
652 }
653
654 /* figure out which file is to be edited */
655 ii = atoi(PD("w","0"));
656 if( ii<0 || ii>count(aSkinAttr) ) ii = 0;
657 zKey = mprintf("draft%d-%s", iSkin, aSkinAttr[ii].zFile);
658
659 zBasis = PD("basis","default");
660 zDflt = mprintf("skins/%s/%s.txt", zBasis, aSkinAttr[ii].zFile);
661 db_begin_transaction();
662 if( P("revert")!=0 ){
663 db_multi_exec("DELETE FROM config WHERE name=%Q", aSkinAttr[ii].zFile);
@@ -642,16 +667,16 @@
667 for(j=0; j<count(aSkinAttr); j++){
668 if( j==ii ) continue;
669 style_submenu_element(aSkinAttr[j].zSubmenu,
670 "%R/setup_skinedit?w=%d&basis=%h",j,zBasis);
671 }
 
672 @ <form action="%s(g.zTop)/setup_skinedit" method="post"><div>
673 login_insert_csrf_secret();
674 @ <input type='hidden' name='w' value='%d(ii)'>
675 @ <input type='hidden' name='sk' value='%d(iSkin)'>
676 @ <h2>Edit %s(aSkinAttr[ii].zTitle):</h2>
677 zContent = textarea_attribute("", 10, 80, zKey,
678 aSkinAttr[ii].zFile, builtin_text(zDflt), 0);
679 @ <br />
680 @ <input type="submit" name="submit" value="Apply Changes" />
681 @ <hr />
682 @ Baseline: <select size='1' name='basis'>
@@ -695,39 +720,83 @@
720 ** Try to initialize draft skin iSkin to the built-in or preexisting
721 ** skin named by zTemplate.
722 */
723 static void skin_initialize_draft(int iSkin, const char *zTemplate){
724 int i;
 
725 if( zTemplate==0 ) return;
726 if( strcmp(zTemplate, "current")==0 ){
727 for(i=0; i<count(azSkinFile); i++){
728 db_unset_mprintf("draft%d-%s", 0, iSkin, azSkinFile[i]);
729 }
730 }else{
731 for(i=0; i<count(aBuiltinSkin); i++){
732 if( strcmp(zTemplate, aBuiltinSkin[i].zLabel)==0 ){
733 for(i=0; i<count(azSkinFile); i++){
734 char *zKey = mprintf("skins/%s/%s.txt", zTemplate, azSkinFile[i]);
735 db_set_mprintf("draft%d-%s", builtin_text(zKey), 0,
736 iSkin, azSkinFile[i]);
737 }
738 break;
739 }
740 }
741 }
742 }
743
744 /*
745 ** Publish the draft skin iSkin as the new default.
746 */
747 static void skin_publish(int iSkin){
748 char *zCurrent; /* SQL description of the current skin */
749 char *zBuiltin; /* SQL description of a built-in skin */
750 int i;
751 int seen = 0; /* True if no need to make a backup */
752
753 /* Check to see if the current skin is already saved. If it is, there
754 ** is no need to create a backup */
755 zCurrent = getSkin(0);
756 for(i=0; i<count(aBuiltinSkin); i++){
757 zBuiltin = getSkin(aBuiltinSkin[i].zLabel);
758 if( fossil_strcmp(aBuiltinSkin[i].zSQL, zCurrent)==0 ){
759 seen = 1;
760 break;
761 }
762 }
763 if( !seen ){
764 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
765 " AND value=%Q", zCurrent);
766 }
767 if( !seen ){
768 db_multi_exec(
769 "INSERT INTO config(name,value,mtime) VALUES("
770 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
771 " %Q,now())", zCurrent
772 );
773 }
774
775 /* Publish draft iSkin */
776 for(i=0; i<count(azSkinFile); i++){
777 db_multi_exec(
778 "UPDATE config"
779 " SET value=(SELECT value FROM config AS x"
780 " WHERE x.name = printf('draft%d-%%s',config.name)),"
781 " mtime=now()"
782 " WHERE name IN ('css','header','footer','details')", iSkin
783 );
784 }
785 }
786
787 /*
788 ** WEBPAGE: setup_skin
789 **
790 ** Generate a page showing the steps needed to customize a skin.
791 */
792 void setup_skin(void){
793 int i; /* Loop counter */
794 int iSkin; /* Which draft skin is being edited */
795 int isSetup; /* True for an administrator */
796 int isEditor; /* Others authorized to make edits */
797 char *zAllowedEditors; /* Who may edit the draft skin */
798 static const char *azTestPages[] = {
799 "home",
800 "timeline",
801 "dir?ci=tip",
802 "dir?ci=tip&type=tree",
@@ -741,27 +810,34 @@
810
811 /* Figure out if the current user is allowed to make administrative
812 ** changes and/or edits
813 */
814 login_check_credentials();
815 zAllowedEditors = db_get_mprintf("draft%d-users", "", iSkin);
816 if( g.perm.Setup ){
817 isSetup = isEditor = 1;
818 }else{
 
819 Glob *pAllowedEditors;
820 isSetup = isEditor = 0;
821 if( zAllowedEditors[0] ){
 
822 pAllowedEditors = glob_create(zAllowedEditors);
823 isEditor = glob_match(pAllowedEditors, zAllowedEditors);
824 glob_free(pAllowedEditors);
825 }
826 }
827
828 /* Initialize the skin, if requested and authorized. */
829 if( P("init3")!=0 && isEditor ){
830 skin_initialize_draft(iSkin, P("initskin"));
831 }
832 if( P("e3")!=0 && isSetup ){
833 db_set_mprintf("draft%d-users", PD("editors",""), 0, iSkin);
834 }
835
836 /* Publish the draft skin */
837 if( P("pub7")!=0 && PB("pub7ck1") && PB("pub7ck2") ){
838 skin_publish(iSkin);
839 }
840
841 style_header("Customize Skin");
842
843 @ <p>Customize the look of this Fossil repository by making changes
@@ -776,11 +852,11 @@
852 @ edits are made to one of nine draft skins. A draft skin can then
853 @ be published to become the default skin.
854 @ Nine separate drafts are available to facilitate A/B testing.</p>
855 @
856 @ <form method='POST' action='%R/setup_skin#step2' id='f01'>
857 @ <p class='skinInput'>Draft skin to edit:
858 @ <select size='1' name='sk' onchange='gebi("f01").submit()'>
859 for(i=1; i<=9; i++){
860 if( i==iSkin ){
861 @ <option value='%d(i)' selected>draft%d(i)</option>
862 }else{
@@ -789,11 +865,36 @@
865 }
866 @ </select>
867 @ </p>
868 @
869 @ <a name='step2'></a>
870 @ <h1>Step 2: Authenticate</h1>
871 @
872 if( isSetup ){
873 @ <p>As an administrator, you can make any edits you like to this or
874 @ any other skin. You can also authorized other users to edit this
875 @ skin. Any user whose login name matches the comma-separate list
876 @ of GLOB expressions below is given special permission to edit
877 @ the draft%d(iSkin) skin:
878 @
879 @ <form method='POST' action='%R/setup_skin#step2' id='f02'>
880 @ <p class='skinInput'>
881 @ <input type='hidden' name='sk' value='%d(iSkin)'>
882 @ Authorized editors for skin draft%d(iSkin):
883 @ <input type='text' name='editors' value='%h(zAllowedEditors)'\
884 @ width='40'>
885 @ <input type='submit' name='submit2' value='Change'>
886 @ </p>
887 @ </form>
888 }else if( isEditor ){
889 @ <p>You are authorized to make changes to the draft%d(iSkin) skin.
890 @ Continue to the <a href='#step3'>next step</a>.</p>
891 }else{
892 @ <p>You are not authorized to make changes to the draft%d(iSkin)
893 @ skin. Contact the administrator of this Fossil repository for
894 @ further information.</p>
895 }
896 @
897 @ <a name='step3'></a>
898 @ <h1>Step 3: Initialize The Draft</h1>
899 @
900 if( !isEditor ){
@@ -801,51 +902,88 @@
902 @ the administrator for this repository for more information.
903 }else{
904 @ <p>Initialize the draft%d(iSkin) skin to one of the built-in skins
905 @ or a preexisting skin, to use as a baseline.</p>
906 @
907 @ <form method='POST' action='%R/setup_skin#step4' id='f03'>
908 @ <p class='skinInput'>
909 @ <input type='hidden' name='sk' value='%d(iSkin)'>
910 @ Initialize skin <b>draft%d(iSkin)</b> using
911 @ <select size='1' name='initskin'>
912 @ <option value='current'>Currently In Use</option>
913 for(i=0; i<count(aBuiltinSkin); i++){
914 @ <option value='%s(aBuiltinSkin[i].zLabel)'>\
915 @ %h(aBuiltinSkin[i].zDesc) (built-in)</option>
916 }
917 @ </select>
918 @ <input type='submit' name='init3' value='Go'>
919 @ </p>
920 @ </form>
921 }
922 @
923 @ <a name='step4'></a>
924 @ <h1>Step 4: Make Edits</h1>
925 @
926 if( !isEditor ){
927 @ <p>You are not authorized to make edits to the draft%d(iSkin) skin.
928 @ Contact the administrator of this Fossil repository for help.</p>
929 }else{
930 @ <p>Edit the components of the draft%d(iSkin) skin:
931 @ <ul>
932 @ <li><a href='%R/setup_skinedit?w=0&sk=%d(iSkin)' target='_blank'>CSS</a>
933 @ <li><a href='%R/setup_skinedit?w=1&sk=%d(iSkin)' target='_blank'>\
934 @ Header</a>
935 @ <li><a href='%R/setup_skinedit?w=2&sk=%d(iSkin)' target='_blank'>\
936 @ Footer</a>
937 @ <li><a href='%R/setup_skinedit?w=3&sk=%d(iSkin)' target='_blank'>\
938 @ Details</a>
939 @ </ul>
940 }
941 @
942 @ <a name='step5'></a>
943 @ <h1>Step 5: Verify The Draft Skin</h1>
944 @
945 @ <p>To test this draft skin, insert text "/draft%d(iSkin)/" just before the
946 @ operation name in the URL. Here are a few links to try:
947 @ <ul>
948 for(i=0; i<count(azTestPages); i++){
949 @ <li><a href='%s(g.zBaseURL)/draft%d(iSkin)/%s(azTestPages[i])' \
950 @ target='_blank'>%s(g.zBaseURL)/draft%d(iSkin)/%s(azTestPages[i])</a>
951 }
952 @ </ul>
953 @
954 @ <p>You will probably need to press Reload on your browser before any
955 @ CSS changes will take effect.</p>
956 @
957 @ <a hame='step6'></a>
958 @ <h1>Step 6: Interate</h1>
959 @
960 @ <p>Repeat <a href='#step4'>step 4</a> and
961 @ <a href='#step5'>step 5</a> as many times as necessary to create
962 @ a production-ready skin.
963 @
964 @ <a name='step6'></a>
965 @ <h1>Step 7: Publish The Draft</h1>
966 @
967 if( !g.perm.Setup ){
968 @ <p>Only administrators are allowed to publish draft skins. Contact
969 @ an administrator to get this "draft%d(iSkin)" skin published.</p>
970 }else{
971 @ <p>When the draft%d(iSkin) skin is ready for production use,
972 @ make it the default scan by clicking the acknowledgements and
973 @ pressing the button below:</p>
974 @
975 @ <form method='POST' action='%R/setup_skin#step7'>
976 @ <p class='skinInput'>
977 @ <input type='hidden' name='sk' value='%d(iSkin)'>
978 @ <input type='checkbox' name='pub7ck1' value='yes'>\
979 @ Skin draft%d(iSkin) has been testing and found ready for production.<br>
980 @ <input type='checkbox' name='pub7ck2' value='yes'>\
981 @ The current skin should be overwritten with draft%d(iSkin).<br>
982 @ <input type='submit' name='pub7' value='Publish Draft%d(iSkin)'>
983 @ </p></form>
984 @
985 @ <p>You will probably need to press Reload on your browser after
986 @ publishing the new skin.</p>
987 }
 
 
 
988 style_footer();
989 }
990

Keyboard Shortcuts

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