Fossil SCM
The new skin editing is working, minimally. Still needs lots of work, though.
Commit
5840fdd73299f9da71f8c9b18720ca5b3a58bd52574e08ae5dd51b5830d8d0ac
Parent
0cba37ec1aae0f5…
1 file changed
+178
-40
+178
-40
| --- src/skins.c | ||
| +++ src/skins.c | ||
| @@ -53,10 +53,15 @@ | ||
| 53 | 53 | { "Black & White, Menu on Left", "black_and_white", 0 }, |
| 54 | 54 | { "Plain Gray, No Logo", "plain_gray", 0 }, |
| 55 | 55 | { "Khaki, No Logo", "khaki", 0 }, |
| 56 | 56 | }; |
| 57 | 57 | |
| 58 | +/* | |
| 59 | +** A skin consists of four "files" named here: | |
| 60 | +*/ | |
| 61 | +static const char *azSkinFile[] = { "css", "header", "footer", "details" }; | |
| 62 | + | |
| 58 | 63 | /* |
| 59 | 64 | ** Alternative skins can be specified in the CGI script or by options |
| 60 | 65 | ** on the "http", "ui", and "server" commands. The alternative skin |
| 61 | 66 | ** name must be one of the aBuiltinSkin[].zLabel names. If there is |
| 62 | 67 | ** a match, that alternative is used. |
| @@ -331,30 +336,29 @@ | ||
| 331 | 336 | ** Memory to hold the returned string is obtained from malloc. |
| 332 | 337 | */ |
| 333 | 338 | static char *getSkin(const char *zName){ |
| 334 | 339 | const char *z; |
| 335 | 340 | char *zLabel; |
| 336 | - static const char *azType[] = { "css", "header", "footer", "details" }; | |
| 337 | 341 | int i; |
| 338 | 342 | Blob val; |
| 339 | 343 | blob_zero(&val); |
| 340 | - for(i=0; i<count(azType); i++){ | |
| 344 | + for(i=0; i<count(azSkinFile); i++){ | |
| 341 | 345 | if( zName ){ |
| 342 | - zLabel = mprintf("skins/%s/%s.txt", zName, azType[i]); | |
| 346 | + zLabel = mprintf("skins/%s/%s.txt", zName, azSkinFile[i]); | |
| 343 | 347 | z = builtin_text(zLabel); |
| 344 | 348 | fossil_free(zLabel); |
| 345 | 349 | }else{ |
| 346 | - z = db_get(azType[i], 0); | |
| 350 | + z = db_get(azSkinFile[i], 0); | |
| 347 | 351 | if( z==0 ){ |
| 348 | - zLabel = mprintf("skins/default/%s.txt", azType[i]); | |
| 352 | + zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]); | |
| 349 | 353 | z = builtin_text(zLabel); |
| 350 | 354 | fossil_free(zLabel); |
| 351 | 355 | } |
| 352 | 356 | } |
| 353 | 357 | blob_appendf(&val, |
| 354 | 358 | "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n", |
| 355 | - azType[i], z | |
| 359 | + azSkinFile[i], z | |
| 356 | 360 | ); |
| 357 | 361 | } |
| 358 | 362 | return blob_str(&val); |
| 359 | 363 | } |
| 360 | 364 | |
| @@ -440,11 +444,11 @@ | ||
| 440 | 444 | |
| 441 | 445 | /* |
| 442 | 446 | ** WEBPAGE: setup_skin_old |
| 443 | 447 | ** |
| 444 | 448 | ** 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. | |
| 446 | 450 | */ |
| 447 | 451 | void setup_skin_old(void){ |
| 448 | 452 | const char *z; |
| 449 | 453 | char *zName; |
| 450 | 454 | char *zErr = 0; |
| @@ -601,13 +605,14 @@ | ||
| 601 | 605 | |
| 602 | 606 | /* |
| 603 | 607 | ** WEBPAGE: setup_skinedit |
| 604 | 608 | ** |
| 605 | 609 | ** Edit aspects of a skin determined by the w= query parameter. |
| 606 | -** Requires Admin privileges. | |
| 610 | +** Requires Setup privileges. | |
| 607 | 611 | ** |
| 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 | |
| 609 | 614 | */ |
| 610 | 615 | void setup_skinedit(void){ |
| 611 | 616 | static const struct sSkinAddr { |
| 612 | 617 | const char *zFile; |
| 613 | 618 | const char *zTitle; |
| @@ -619,20 +624,40 @@ | ||
| 619 | 624 | /* 3 */ { "details", "Display Details", "Details", }, |
| 620 | 625 | }; |
| 621 | 626 | const char *zBasis; |
| 622 | 627 | const char *zContent; |
| 623 | 628 | char *zDflt; |
| 629 | + char *zKey; | |
| 630 | + int iSkin; | |
| 624 | 631 | int ii; |
| 625 | 632 | int j; |
| 626 | 633 | |
| 627 | 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. */ | |
| 628 | 641 | 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 | + } | |
| 631 | 652 | } |
| 653 | + | |
| 654 | + /* figure out which file is to be edited */ | |
| 632 | 655 | ii = atoi(PD("w","0")); |
| 633 | 656 | if( ii<0 || ii>count(aSkinAttr) ) ii = 0; |
| 657 | + zKey = mprintf("draft%d-%s", iSkin, aSkinAttr[ii].zFile); | |
| 658 | + | |
| 634 | 659 | zBasis = PD("basis","default"); |
| 635 | 660 | zDflt = mprintf("skins/%s/%s.txt", zBasis, aSkinAttr[ii].zFile); |
| 636 | 661 | db_begin_transaction(); |
| 637 | 662 | if( P("revert")!=0 ){ |
| 638 | 663 | db_multi_exec("DELETE FROM config WHERE name=%Q", aSkinAttr[ii].zFile); |
| @@ -642,16 +667,16 @@ | ||
| 642 | 667 | for(j=0; j<count(aSkinAttr); j++){ |
| 643 | 668 | if( j==ii ) continue; |
| 644 | 669 | style_submenu_element(aSkinAttr[j].zSubmenu, |
| 645 | 670 | "%R/setup_skinedit?w=%d&basis=%h",j,zBasis); |
| 646 | 671 | } |
| 647 | - style_submenu_element("Skins", "%R/setup_skin_old"); | |
| 648 | 672 | @ <form action="%s(g.zTop)/setup_skinedit" method="post"><div> |
| 649 | 673 | login_insert_csrf_secret(); |
| 650 | 674 | @ <input type='hidden' name='w' value='%d(ii)'> |
| 675 | + @ <input type='hidden' name='sk' value='%d(iSkin)'> | |
| 651 | 676 | @ <h2>Edit %s(aSkinAttr[ii].zTitle):</h2> |
| 652 | - zContent = textarea_attribute("", 10, 80, aSkinAttr[ii].zFile, | |
| 677 | + zContent = textarea_attribute("", 10, 80, zKey, | |
| 653 | 678 | aSkinAttr[ii].zFile, builtin_text(zDflt), 0); |
| 654 | 679 | @ <br /> |
| 655 | 680 | @ <input type="submit" name="submit" value="Apply Changes" /> |
| 656 | 681 | @ <hr /> |
| 657 | 682 | @ Baseline: <select size='1' name='basis'> |
| @@ -695,39 +720,83 @@ | ||
| 695 | 720 | ** Try to initialize draft skin iSkin to the built-in or preexisting |
| 696 | 721 | ** skin named by zTemplate. |
| 697 | 722 | */ |
| 698 | 723 | static void skin_initialize_draft(int iSkin, const char *zTemplate){ |
| 699 | 724 | int i; |
| 700 | - const char *azWhat[] = { "css", "header", "footer", "detail" }; | |
| 701 | 725 | if( zTemplate==0 ) return; |
| 702 | 726 | 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]); | |
| 705 | 729 | } |
| 706 | 730 | }else{ |
| 707 | 731 | for(i=0; i<count(aBuiltinSkin); i++){ |
| 708 | 732 | 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]); | |
| 712 | 737 | } |
| 713 | 738 | break; |
| 714 | 739 | } |
| 715 | 740 | } |
| 716 | 741 | } |
| 717 | 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 | +} | |
| 718 | 786 | |
| 719 | 787 | /* |
| 720 | 788 | ** WEBPAGE: setup_skin |
| 721 | 789 | ** |
| 722 | 790 | ** Generate a page showing the steps needed to customize a skin. |
| 723 | 791 | */ |
| 724 | 792 | void setup_skin(void){ |
| 725 | 793 | int i; /* Loop counter */ |
| 726 | 794 | int iSkin; /* Which draft skin is being edited */ |
| 727 | - int isAdmin; /* True for an administrator */ | |
| 795 | + int isSetup; /* True for an administrator */ | |
| 728 | 796 | int isEditor; /* Others authorized to make edits */ |
| 797 | + char *zAllowedEditors; /* Who may edit the draft skin */ | |
| 729 | 798 | static const char *azTestPages[] = { |
| 730 | 799 | "home", |
| 731 | 800 | "timeline", |
| 732 | 801 | "dir?ci=tip", |
| 733 | 802 | "dir?ci=tip&type=tree", |
| @@ -741,27 +810,34 @@ | ||
| 741 | 810 | |
| 742 | 811 | /* Figure out if the current user is allowed to make administrative |
| 743 | 812 | ** changes and/or edits |
| 744 | 813 | */ |
| 745 | 814 | 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; | |
| 748 | 818 | }else{ |
| 749 | - char *zAllowedEditors; | |
| 750 | 819 | 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] ){ | |
| 754 | 822 | pAllowedEditors = glob_create(zAllowedEditors); |
| 755 | 823 | isEditor = glob_match(pAllowedEditors, zAllowedEditors); |
| 756 | 824 | glob_free(pAllowedEditors); |
| 757 | 825 | } |
| 758 | 826 | } |
| 759 | 827 | |
| 760 | 828 | /* Initialize the skin, if requested and authorized. */ |
| 761 | 829 | if( P("init3")!=0 && isEditor ){ |
| 762 | 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); | |
| 763 | 839 | } |
| 764 | 840 | |
| 765 | 841 | style_header("Customize Skin"); |
| 766 | 842 | |
| 767 | 843 | @ <p>Customize the look of this Fossil repository by making changes |
| @@ -776,11 +852,11 @@ | ||
| 776 | 852 | @ edits are made to one of nine draft skins. A draft skin can then |
| 777 | 853 | @ be published to become the default skin. |
| 778 | 854 | @ Nine separate drafts are available to facilitate A/B testing.</p> |
| 779 | 855 | @ |
| 780 | 856 | @ <form method='POST' action='%R/setup_skin#step2' id='f01'> |
| 781 | - @ <p>Skin to edit: | |
| 857 | + @ <p class='skinInput'>Draft skin to edit: | |
| 782 | 858 | @ <select size='1' name='sk' onchange='gebi("f01").submit()'> |
| 783 | 859 | for(i=1; i<=9; i++){ |
| 784 | 860 | if( i==iSkin ){ |
| 785 | 861 | @ <option value='%d(i)' selected>draft%d(i)</option> |
| 786 | 862 | }else{ |
| @@ -789,11 +865,36 @@ | ||
| 789 | 865 | } |
| 790 | 866 | @ </select> |
| 791 | 867 | @ </p> |
| 792 | 868 | @ |
| 793 | 869 | @ <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 | + } | |
| 795 | 896 | @ |
| 796 | 897 | @ <a name='step3'></a> |
| 797 | 898 | @ <h1>Step 3: Initialize The Draft</h1> |
| 798 | 899 | @ |
| 799 | 900 | if( !isEditor ){ |
| @@ -801,51 +902,88 @@ | ||
| 801 | 902 | @ the administrator for this repository for more information. |
| 802 | 903 | }else{ |
| 803 | 904 | @ <p>Initialize the draft%d(iSkin) skin to one of the built-in skins |
| 804 | 905 | @ or a preexisting skin, to use as a baseline.</p> |
| 805 | 906 | @ |
| 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'> | |
| 807 | 909 | @ <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 | |
| 809 | 911 | @ <select size='1' name='initskin'> |
| 810 | 912 | @ <option value='current'>Currently In Use</option> |
| 811 | 913 | for(i=0; i<count(aBuiltinSkin); i++){ |
| 812 | 914 | @ <option value='%s(aBuiltinSkin[i].zLabel)'>\ |
| 813 | 915 | @ %h(aBuiltinSkin[i].zDesc) (built-in)</option> |
| 814 | 916 | } |
| 815 | 917 | @ </select> |
| 816 | 918 | @ <input type='submit' name='init3' value='Go'> |
| 817 | 919 | @ </p> |
| 920 | + @ </form> | |
| 818 | 921 | } |
| 819 | 922 | @ |
| 820 | 923 | @ <a name='step4'></a> |
| 821 | 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 | + } | |
| 822 | 941 | @ |
| 823 | 942 | @ <a name='step5'></a> |
| 824 | 943 | @ <h1>Step 5: Verify The Draft Skin</h1> |
| 825 | 944 | @ |
| 826 | 945 | @ <p>To test this draft skin, insert text "/draft%d(iSkin)/" just before the |
| 827 | 946 | @ operation name in the URL. Here are a few links to try: |
| 828 | 947 | @ <ul> |
| 829 | 948 | 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> | |
| 832 | 951 | } |
| 833 | 952 | @ </ul> |
| 834 | 953 | @ |
| 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. | |
| 838 | 963 | @ |
| 839 | 964 | @ <a name='step6'></a> |
| 840 | - @ <h1>Step 6: Publish The Draft</h1> | |
| 965 | + @ <h1>Step 7: Publish The Draft</h1> | |
| 966 | + @ | |
| 841 | 967 | if( !g.perm.Setup ){ |
| 842 | 968 | @ <p>Only administrators are allowed to publish draft skins. Contact |
| 843 | 969 | @ an administrator to get this "draft%d(iSkin)" skin published.</p> |
| 844 | 970 | }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> | |
| 846 | 987 | } |
| 847 | - @ | |
| 848 | - @ <a name='step7'></a> | |
| 849 | - @ <h1>Step 7: Cleanup</h1> | |
| 850 | 988 | style_footer(); |
| 851 | 989 | } |
| 852 | 990 |
| --- 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 |