mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
- Cosmetics: aligning the style in the different pages, make the iTop logo clickable, etc...
- Handle persistent user preferences: for now only the menu status (open/closed) and its width. In the future the status of lists (open/closed, which columns to show...) can be considered as well. SVN:trunk[487]
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
require_once("../application/nicewebpage.class.inc.php");
|
||||
require_once("../application/usercontext.class.inc.php");
|
||||
require_once("../application/applicationcontext.class.inc.php");
|
||||
require_once("../application/user.preferences.class.inc.php");
|
||||
/**
|
||||
* Web page with some associated CSS and scripts (jquery) for a fancier display
|
||||
*/
|
||||
@@ -95,15 +96,44 @@ class iTopWebPage extends NiceWebPage
|
||||
|
||||
$(document).ready(function () {
|
||||
// Layout
|
||||
paneSize = GetUserPreference('menu_size', 300)
|
||||
myLayout = $('body').layout({
|
||||
west : { minSize: 200, size: 300 /* TO DO: read from a cookie ?*/, spacing_open: 16, spacing_close: 16, slideTrigger_open: "mouseover", hideTogglerOnSlide: true }
|
||||
west : {
|
||||
minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "mouseover", hideTogglerOnSlide: true,
|
||||
onclose_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
SetUserPreference('menu_pane', 'closed', true);
|
||||
}
|
||||
},
|
||||
onresize_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
SetUserPreference('menu_size', state.size, true);
|
||||
}
|
||||
},
|
||||
|
||||
onopen_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
SetUserPreference('menu_pane', 'open', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
myLayout.addPinBtn( "#tPinMenu", "west" );
|
||||
//myLayout.open( "west" );
|
||||
$('.ui-layout-resizer-west').html('<img src="../images/splitter-top-corner.png"/>');
|
||||
if (GetUserPreference('menu_pane', 'open') == 'closed')
|
||||
{
|
||||
myLayout.close('west');
|
||||
}
|
||||
|
||||
// Accordion Menu
|
||||
$("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false });
|
||||
$("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false });
|
||||
});
|
||||
|
||||
//$("div[id^=tabbedContent] > ul").tabs( 1, { fxFade: true, fxSpeed: 'fast' } ); // tabs
|
||||
@@ -128,11 +158,13 @@ class iTopWebPage extends NiceWebPage
|
||||
catch(err)
|
||||
{
|
||||
// Do something with the error !
|
||||
alert(err);
|
||||
}
|
||||
|
||||
//$('.display_block').draggable(); // make the blocks draggable
|
||||
EOF
|
||||
);
|
||||
$sUserPrefs = appUserPreferences::GetAsJSON();
|
||||
$this->add_script("
|
||||
// For automplete
|
||||
function findValue(li) {
|
||||
@@ -173,6 +205,8 @@ EOF
|
||||
$('#rawOutput').dialog( {autoOpen: true, modal:false});
|
||||
}
|
||||
}
|
||||
|
||||
var oUserPreferences = $sUserPrefs;
|
||||
");
|
||||
|
||||
// Add the standard menus
|
||||
@@ -223,36 +257,56 @@ EOF
|
||||
|
||||
public function GetSiloSelectionForm()
|
||||
{
|
||||
$sHtml = '<div id="SiloSelection">';
|
||||
$sHtml .= '<form style="display:inline" action="'.$_SERVER['PHP_SELF'].'"><select style="width:150px;font-size:x-small" name="org_id" title="Pick an organization" onChange="this.form.submit();">';
|
||||
// List of visible Organizations
|
||||
$iCount = 0;
|
||||
$oContext = new UserContext();
|
||||
$oSearchFilter = $oContext->NewFilter("bizOrganization");
|
||||
$oSet = new CMDBObjectSet($oSearchFilter);
|
||||
$sSelected = ($this->m_currentOrganization == '') ? ' selected' : '';
|
||||
$sHtml .= '<option value="">'.Dict::S('UI:AllOrganizations').'</option>';
|
||||
if ($oSet->Count() > 0)
|
||||
while($oOrg = $oSet->Fetch())
|
||||
if (MetaModel::IsValidClass('bizOrganization'))
|
||||
{
|
||||
if ($this->m_currentOrganization == $oOrg->GetKey())
|
||||
{
|
||||
$oCurrentOrganization = $oOrg;
|
||||
$sSelected = " selected";
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSelected = "";
|
||||
}
|
||||
$sHtml .= '<option value="'.$oOrg->GetKey().'"'.$sSelected.'>'.$oOrg->Get('name').'</option>';
|
||||
$oSearchFilter = $oContext->NewFilter('Organization');
|
||||
$oSet = new CMDBObjectSet($oSearchFilter);
|
||||
$iCount = $oSet->Count();
|
||||
}
|
||||
switch($iCount)
|
||||
{
|
||||
case 0:
|
||||
// No such dimension/silo => nothing to select
|
||||
$sHtml = '<div id="SiloSelection"><!-- nothing to select --></div>';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Only one possible choice... no selection, but display the value
|
||||
$oOrg = $oSet->Fetch();
|
||||
$sHtml = '<div id="SiloSelection">'.$oOrg->GetName().'</div>';
|
||||
$sHtml .= '';
|
||||
break;
|
||||
|
||||
default:
|
||||
$sHtml = '<div id="SiloSelection">';
|
||||
$sHtml .= '<form style="display:inline" action="'.$_SERVER['PHP_SELF'].'"><select style="width:150px;font-size:x-small" name="org_id" title="Pick an organization" onChange="this.form.submit();">';
|
||||
$sSelected = ($this->m_currentOrganization == '') ? ' selected' : '';
|
||||
$sHtml .= '<option value=""'.$sSelected.'>'.Dict::S('UI:AllOrganizations').'</option>';
|
||||
while($oOrg = $oSet->Fetch())
|
||||
{
|
||||
if ($this->m_currentOrganization == $oOrg->GetKey())
|
||||
{
|
||||
$oCurrentOrganization = $oOrg;
|
||||
$sSelected = " selected";
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSelected = "";
|
||||
}
|
||||
$sHtml .= '<option value="'.$oOrg->GetKey().'"'.$sSelected.'>'.$oOrg->GetName().'</option>';
|
||||
}
|
||||
$sHtml .= '</select>';
|
||||
// Add other dimensions/context information to this form
|
||||
$oAppContext = new ApplicationContext();
|
||||
$oAppContext->Reset('org_id'); // Org id is handled above and we want to be able to change it here !
|
||||
$sHtml .= $oAppContext->GetForForm();
|
||||
$sHtml .= '</form>';
|
||||
$sHtml .= '</div>';
|
||||
}
|
||||
$sHtml .= '</select>';
|
||||
// Add other dimensions/context information to this form
|
||||
$oAppContext = new ApplicationContext();
|
||||
$oAppContext->Reset('org_id'); // Org id is handled above and we want to be able to change it here !
|
||||
$sHtml .= $oAppContext->GetForForm();
|
||||
$sHtml .= '</form>';
|
||||
$sHtml .= '</div>';
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -341,12 +395,12 @@ EOF
|
||||
if (ITOP_REVISION == '$WCREV$')
|
||||
{
|
||||
// This is NOT a version built using the buil system, just display the main version
|
||||
$sVersionString = "iTop Version ".ITOP_VERSION;
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a build made from SVN, let display the full information
|
||||
$sVersionString = "iTop Version ".ITOP_VERSION." revision ".ITOP_REVISION.", built on: ".ITOP_BUILD_DATE;
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
|
||||
}
|
||||
|
||||
// Render the text of the global search form
|
||||
@@ -413,7 +467,7 @@ EOF
|
||||
echo '<div id="left-pane" class="ui-layout-west">';
|
||||
echo '<!-- Beginning of the left pane -->';
|
||||
echo ' <div id="header-logo">';
|
||||
echo ' <div id="top-left"></div><div id="logo"><img src="../images/itop-logo.png" style="margin-top:16px; margin-right:40px;"/></div>';
|
||||
echo ' <div id="top-left"></div><div id="logo"><a href="http://www.combodo.com/itop"><img src="../images/itop-logo.png" title="'.$sVersionString.'" style="border:0; margin-top:16px; margin-right:40px;"/></a></div>';
|
||||
echo ' </div>';
|
||||
echo ' <div class="header-menu">';
|
||||
echo ' <div class="icon ui-state-default ui-corner-all"><span id="tPinMenu" class="ui-icon ui-icon-pin-w">pin</span></div>';
|
||||
|
||||
@@ -32,33 +32,56 @@ class LoginWebPage extends NiceWebPage
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct("iTop Login");
|
||||
$this->add_style("
|
||||
$this->add_style(<<<EOF
|
||||
body {
|
||||
background-color: #eee;
|
||||
background: #eee;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#login {
|
||||
width: 230px;
|
||||
#login-logo {
|
||||
margin-top: 150px;
|
||||
width: 250px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background: #f6f6f1;
|
||||
height: 54px;
|
||||
border-top: 1px solid #000;
|
||||
border-left: 1px solid #000;
|
||||
border-right: 1px solid #000;
|
||||
border-bottom: 0;
|
||||
text-align: center;
|
||||
}
|
||||
#login-logo img {
|
||||
border: 0;
|
||||
}
|
||||
#login {
|
||||
width: 250px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 150px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
border-left: 1px solid #000;
|
||||
border-right: 1px solid #000;
|
||||
border-top: 0;
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #83b217;
|
||||
color: #1C94C4;
|
||||
font-size: 16pt;
|
||||
}
|
||||
.v-spacer {
|
||||
padding-top: 1em;
|
||||
}
|
||||
");
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
public function DisplayLoginForm($bFailedLogin = false)
|
||||
@@ -66,6 +89,8 @@ h1 {
|
||||
$sAuthUser = utils::ReadParam('auth_user', '');
|
||||
$sAuthPwd = utils::ReadParam('suggest_pwd', '');
|
||||
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
|
||||
$this->add("<div id=\"login-logo\"><a href=\"http://www.combodo.com/itop\"><img title=\"$sVersionShort\" src=\"../images/itop-logo.png\"></a></div>\n");
|
||||
$this->add("<div id=\"login\">\n");
|
||||
$this->add("<h1>".Dict::S('UI:Login:Welcome')."</h1>\n");
|
||||
if ($bFailedLogin)
|
||||
|
||||
@@ -276,7 +276,7 @@ abstract class MenuNode
|
||||
public function GetHyperlink($aExtraParams)
|
||||
{
|
||||
$aExtraParams['menu'] = $this->GetIndex();
|
||||
return $this->AddParams('../pages/UI.php?menu=', $aExtraParams);
|
||||
return $this->AddParams('../pages/UI.php', $aExtraParams);
|
||||
}
|
||||
|
||||
public abstract function RenderContent(WebPage $oPage, $aExtraParams = array());
|
||||
|
||||
162
application/user.preferences.class.inc.php
Normal file
162
application/user.preferences.class.inc.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
// Copyright (C) 2010 Combodo SARL
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; version 3 of the License.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
/**
|
||||
* Store and retrieve user's preferences (i.e persistent per user settings)
|
||||
*
|
||||
* @author Erwan Taloc <erwan.taloc@combodo.com>
|
||||
* @author Romain Quetiez <romain.quetiez@combodo.com>
|
||||
* @author Denis Flaven <denis.flaven@combodo.com>
|
||||
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
|
||||
*/
|
||||
require_once('../core/dbobject.class.php');
|
||||
require_once('../core/userrights.class.inc.php');
|
||||
|
||||
/**
|
||||
* This class is used to store, in a persistent manner, user related settings (preferences)
|
||||
* For each user, one record in the database will be created, making their preferences permanent and storing a list
|
||||
* of properties (pairs of name/value strings)
|
||||
* This overcomes some limitations of cookies: limited number of cookies, maximum size, depends on the browser, etc..
|
||||
* This class is used in conjunction with the GetUserPreferences/SetUserPreferences javascript functions (utils.js)
|
||||
*/
|
||||
class appUserPreferences extends DBObject
|
||||
{
|
||||
static $oUserPrefs = null; // Local cache
|
||||
|
||||
/**
|
||||
* Get the value of the given property/preference
|
||||
* If not set, the default value will be returned
|
||||
* @param string $sCode Code/Name of the property to set
|
||||
* @param string $sDefaultValue The default value
|
||||
* @return string The value of the property for the current user
|
||||
*/
|
||||
static function GetPref($sCode, $sDefaultValue)
|
||||
{
|
||||
if (self::$oUserPrefs == null)
|
||||
{
|
||||
self::Load();
|
||||
}
|
||||
$aPrefs = self::$oUserPrefs->Get('preferences');
|
||||
if (isset($aPrefs[$sCode]))
|
||||
{
|
||||
return $aPrefs[$sCode];
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for a given preference, and stores it into the database
|
||||
* @param string $sCode Code/Name of the property/preference to set
|
||||
* @param string $sValue Value to set
|
||||
*/
|
||||
static function SetPref($sCode, $sValue)
|
||||
{
|
||||
if (self::$oUserPrefs == null)
|
||||
{
|
||||
self::Load();
|
||||
}
|
||||
$aPrefs = self::$oUserPrefs->Get('preferences');
|
||||
$aPrefs[$sCode] = $sValue;
|
||||
self::$oUserPrefs->Set('preferences', $aPrefs);
|
||||
self::Save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function to get all the preferences for the user, packed as a JSON object
|
||||
* @return string JSON representation of the preferences
|
||||
*/
|
||||
static function GetAsJSON()
|
||||
{
|
||||
if (self::$oUserPrefs == null)
|
||||
{
|
||||
self::Load();
|
||||
}
|
||||
$aPrefs = self::$oUserPrefs->Get('preferences');
|
||||
return json_encode($aPrefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function if the user has changed (like when doing a logoff...)
|
||||
*/
|
||||
static public function Reset()
|
||||
{
|
||||
self::$oUserPrefs = null;
|
||||
}
|
||||
/**
|
||||
* Call this function to ERASE all the preferences from the current user
|
||||
*/
|
||||
static public function ClearPreferences()
|
||||
{
|
||||
self::$oUserPrefs = null;
|
||||
}
|
||||
|
||||
static protected function Save()
|
||||
{
|
||||
if (self::$oUserPrefs != null)
|
||||
{
|
||||
if (self::$oUserPrefs->IsModified())
|
||||
{
|
||||
self::$oUserPrefs->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the preferences for the current user, creating the record in the database
|
||||
* if needed
|
||||
*/
|
||||
static protected function Load()
|
||||
{
|
||||
if (self::$oUserPrefs != null) return;
|
||||
$oSearch = new DBObjectSearch('appUserPreferences');
|
||||
$oSearch->AddCondition('userid', UserRights::GetUser());
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$oObj = $oSet->Fetch();
|
||||
if ($oObj == null)
|
||||
{
|
||||
// No prefs (yet) for this user, create the object
|
||||
$oObj = new appUserPreferences();
|
||||
$oObj->Set('userid', UserRights::GetUser());
|
||||
$oObj->Set('preferences', array()); // Default preferences: an empty array
|
||||
$oObj->DBInsert();
|
||||
}
|
||||
self::$oUserPrefs = $oObj;
|
||||
}
|
||||
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "userid",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_app_preferences",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"URP_Users", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributePropertySet("preferences", array("allowed_values"=>null, "sql"=>"preferences", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -468,6 +468,7 @@ class Config
|
||||
fwrite($hFile, "\t'application' => array (\n");
|
||||
fwrite($hFile, "\t\t'../application/transaction.class.inc.php',\n");
|
||||
fwrite($hFile, "\t\t'../application/menunode.class.inc.php',\n");
|
||||
fwrite($hFile, "\t\t'../application/user.preferences.class.inc.php',\n");
|
||||
fwrite($hFile, "\t\t'../application/audit.rule.class.inc.php',\n");
|
||||
// Romain - That's dirty, because those 3 classes are in fact part of the core
|
||||
// but I needed those classes to be derived from cmdbAbstractObject
|
||||
|
||||
@@ -875,6 +875,9 @@ When associated with a trigger, each action is given an "order" number, specifyi
|
||||
'UI:UserAccountsMenu' => 'User Accounts',
|
||||
'UI:UserAccountsMenu+' => 'User Accounts',
|
||||
'UI:UserAccountsMenu:Title' => 'User Accounts',
|
||||
|
||||
'UI:iTopVersion:Short' => 'iTop version %1$s',
|
||||
'UI:iTopVersion:Long' => 'iTop version %1$s-%2$s built on %3$s',
|
||||
));
|
||||
|
||||
|
||||
|
||||
@@ -874,6 +874,9 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
|
||||
'UI:UserAccountsMenu+' => 'Comptes Utilisateur',
|
||||
'UI:UserAccountsMenu:Title' => 'Comptes Utilisateur',
|
||||
|
||||
'UI:iTopVersion:Short' => 'iTop version %1$s',
|
||||
'UI:iTopVersion:Long' => 'iTop version %1$s-%2$s du %3$s',
|
||||
|
||||
));
|
||||
|
||||
?>
|
||||
|
||||
41
js/utils.js
41
js/utils.js
@@ -77,4 +77,43 @@ function ReloadSearchForm(divId, sClassName, sBaseClass)
|
||||
$('#'+divId).unblock();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores - in a persistent way - user specific preferences
|
||||
* depends on a global variable oUserPreferences created/filled by the iTopWebPage
|
||||
* that acts as a local -write through- cache
|
||||
*/
|
||||
function SetUserPreference(sPreferenceCode, sPrefValue, bPersistent)
|
||||
{
|
||||
sPreviousValue = undefined;
|
||||
try
|
||||
{
|
||||
sPreviousValue = oUserPreferences[sPreferenceCode];
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
sPreviousValue = undefined;
|
||||
}
|
||||
oUserPreferences[sPreferenceCode] = sPrefValue;
|
||||
if (bPersistent && (sPrefValue != sPreviousValue))
|
||||
{
|
||||
ajax_request = $.post('ajax.render.php',
|
||||
{ operation: 'set_pref', code: sPreferenceCode, value: sPrefValue} ); // Make it persistent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user specific preferences
|
||||
* depends on a global variable oUserPreferences created/filled by the iTopWebPage
|
||||
* that acts as a local -write through- cache
|
||||
*/
|
||||
function GetUserPreference(sPreferenceCode, sDefaultValue)
|
||||
{
|
||||
var value = sDefaultValue;
|
||||
if ( oUserPreferences[sPreferenceCode] != undefined)
|
||||
{
|
||||
value = oUserPreferences[sPreferenceCode];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ require_once('../application/wizardhelper.class.inc.php');
|
||||
require_once('../application/ui.linkswidget.class.inc.php');
|
||||
|
||||
require_once('../application/startup.inc.php');
|
||||
require_once('../application/user.preferences.class.inc.php');
|
||||
|
||||
session_start();
|
||||
if (isset($_SESSION['auth_user']))
|
||||
{
|
||||
@@ -330,6 +332,17 @@ switch($operation)
|
||||
$sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId, 'baseClass' => $sRootClass));
|
||||
$oPage->add($sHtml);
|
||||
break;
|
||||
|
||||
case 'set_pref':
|
||||
$sCode = utils::ReadParam('code', '', 'post');
|
||||
$sValue = utils::ReadParam('value', '', 'post');
|
||||
appUserPreferences::SetPref($sCode, $sValue);
|
||||
break;
|
||||
|
||||
case 'erase_all_pref':
|
||||
// Can be useful in case a user got some corrupted prefs...
|
||||
appUserPreferences::ClearPreferences();
|
||||
break;
|
||||
|
||||
default:
|
||||
$oPage->p("Invalid query.");
|
||||
|
||||
@@ -55,7 +55,7 @@ body {
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #83b217;
|
||||
color: #1C94C4;
|
||||
font-size: 16pt;
|
||||
}
|
||||
h2 {
|
||||
|
||||
Reference in New Issue
Block a user