diff --git a/.gitignore b/.gitignore index 3418d8ee54..8a7f5c65c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ avatar/* +background/* files/* file/* _darcs/* diff --git a/README b/README index 3279f7bbaa..5aa7270eec 100644 --- a/README +++ b/README @@ -1223,6 +1223,7 @@ supported: an array of mime types you accept to store and distribute, like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you setup your server to properly reckognize the types you want to support. +uploads: false to disable uploading files with notices (true by default). For quotas, be sure you've set the upload_max_filesize and post_max_size in php.ini to be large enough to handle your upload. In httpd.conf @@ -1246,6 +1247,14 @@ Options for group functionality. maxaliases: maximum number of aliases a group can have. Default 3. Set to 0 or less to prevent aliases in a group. + +oohembed +-------- + +oEmbed endpoint for multimedia attachments (links in posts). + +endpoint: oohembed endpoint using http://oohembed.com/ software. + Troubleshooting =============== diff --git a/actions/attachment.php b/actions/attachment.php index 16ee723d96..e4dc0e054e 100644 --- a/actions/attachment.php +++ b/actions/attachment.php @@ -98,48 +98,6 @@ class AttachmentAction extends Action return $a->title(); } - /** - * Last-modified date for page - * - * When was the content of this page last modified? Based on notice, - * profile, avatar. - * - * @return int last-modified date as unix timestamp - */ -/* - function lastModified() - { - return max(strtotime($this->notice->created), - strtotime($this->profile->modified), - ($this->avatar) ? strtotime($this->avatar->modified) : 0); - } -*/ - - /** - * An entity tag for this page - * - * Shows the ETag for the page, based on the notice ID and timestamps - * for the notice, profile, and avatar. It's weak, since we change - * the date text "one hour ago", etc. - * - * @return string etag - */ -/* - function etag() - { - $avtime = ($this->avatar) ? - strtotime($this->avatar->modified) : 0; - - return 'W/"' . implode(':', array($this->arg('action'), - common_language(), - $this->notice->id, - strtotime($this->notice->created), - strtotime($this->profile->modified), - $avtime)) . '"'; - } -*/ - - /** * Handle input * diff --git a/actions/attachment_ajax.php b/actions/attachment_ajax.php index 3d83393c51..5d6773010f 100644 --- a/actions/attachment_ajax.php +++ b/actions/attachment_ajax.php @@ -74,46 +74,5 @@ class Attachment_ajaxAction extends AttachmentAction } $this->elementEnd('div'); } - - /** - * Last-modified date for page - * - * When was the content of this page last modified? Based on notice, - * profile, avatar. - * - * @return int last-modified date as unix timestamp - */ -/* - function lastModified() - { - return max(strtotime($this->notice->created), - strtotime($this->profile->modified), - ($this->avatar) ? strtotime($this->avatar->modified) : 0); - } -*/ - - /** - * An entity tag for this page - * - * Shows the ETag for the page, based on the notice ID and timestamps - * for the notice, profile, and avatar. It's weak, since we change - * the date text "one hour ago", etc. - * - * @return string etag - */ -/* - function etag() - { - $avtime = ($this->avatar) ? - strtotime($this->avatar->modified) : 0; - - return 'W/"' . implode(':', array($this->arg('action'), - common_language(), - $this->notice->id, - strtotime($this->notice->created), - strtotime($this->profile->modified), - $avtime)) . '"'; - } -*/ } diff --git a/actions/block.php b/actions/block.php index 0efee5932c..441016d4e9 100644 --- a/actions/block.php +++ b/actions/block.php @@ -125,16 +125,18 @@ class BlockAction extends Action function areYouSureForm() { $id = $this->profile->id; + $this->elementStart('form', array('id' => 'block-' . $id, + 'method' => 'post', + 'class' => 'form_settings form_entity_block', + 'action' => common_local_url('block'))); + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->element('legend', _('Block user')); $this->element('p', null, _('Are you sure you want to block this user? '. 'Afterwards, they will be unsubscribed from you, '. 'unable to subscribe to you in the future, and '. 'you will not be notified of any @-replies from them.')); - $this->elementStart('form', array('id' => 'block-' . $id, - 'method' => 'post', - 'class' => 'block', - 'action' => common_local_url('block'))); - $this->hidden('token', common_session_token()); $this->element('input', array('id' => 'blockto-' . $id, 'name' => 'blockto', 'type' => 'hidden', @@ -144,8 +146,9 @@ class BlockAction extends Action $this->hidden($k, $v); } } - $this->submit('no', _('No')); - $this->submit('yes', _('Yes')); + $this->submit('form_action-no', _('No'), 'submit form_action-primary', 'no', _("Do not block this user from this group")); + $this->submit('form_action-yes', _('Yes'), 'submit form_action-secondary', 'yes', _('Block this user from this group')); + $this->elementEnd('fieldset'); $this->elementEnd('form'); } diff --git a/actions/designsettings.php b/actions/designsettings.php index 5774b85378..047059e04b 100644 --- a/actions/designsettings.php +++ b/actions/designsettings.php @@ -22,6 +22,7 @@ * @category Settings * @package Laconica * @author Sarven Capadisli + * @author Zach Copley * @copyright 2008-2009 Control Yourself, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ @@ -31,9 +32,8 @@ if (!defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/accountsettingsaction.php'; - - +require_once INSTALLDIR . '/lib/accountsettingsaction.php'; +require_once INSTALLDIR . '/lib/webcolor.php'; class DesignsettingsAction extends AccountSettingsAction { @@ -56,7 +56,8 @@ class DesignsettingsAction extends AccountSettingsAction function getInstructions() { - return _('Customize the way your profile looks with a background image and a colour palette of your choice.'); + return _('Customize the way your profile looks ' . + 'with a background image and a colour palette of your choice.'); } /** @@ -70,29 +71,87 @@ class DesignsettingsAction extends AccountSettingsAction function showContent() { $user = common_current_user(); + $design = $user->getDesign(); + + if (empty($design)) { + $design = $this->defaultDesign(); + } + $this->elementStart('form', array('method' => 'post', + 'enctype' => 'multipart/form-data', 'id' => 'form_settings_design', 'class' => 'form_settings', 'action' => - common_local_url('designsettings'))); + common_local_url('designsettings'))); $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); - $this->elementStart('fieldset', array('id' => 'settings_design_background-image')); + $this->elementStart('fieldset', array('id' => + 'settings_design_background-image')); $this->element('legend', null, _('Change background image')); $this->elementStart('ul', 'form_data'); $this->elementStart('li'); - $this->element('label', array('for' => 'design_background-image_file'), + $this->element('label', array('for' => 'design_background-image_file'), _('Upload file')); $this->element('input', array('name' => 'design_background-image_file', 'type' => 'file', 'id' => 'design_background-image_file')); - $this->element('p', 'form_guide', _('You can upload your personal background image. The maximum file size is 2Mb.')); + $this->element('p', 'form_guide', _('You can upload your personal ' . + 'background image. The maximum file size is 2Mb.')); $this->element('input', array('name' => 'MAX_FILE_SIZE', 'type' => 'hidden', 'id' => 'MAX_FILE_SIZE', 'value' => ImageFile::maxFileSizeInt())); $this->elementEnd('li'); + + if (!empty($design->backgroundimage)) { + + $this->elementStart('li', array('id' => 'design_background-image_onoff')); + + $this->element('img', array('src' => + Design::url($design->backgroundimage))); + + $attrs = array('name' => 'design_background-image_onoff', + 'type' => 'radio', + 'id' => 'design_background-image_on', + 'class' => 'radio', + 'value' => 'on'); + + if ($design->disposition & BACKGROUND_ON) { + $attrs['checked'] = 'checked'; + } + + $this->element('input', $attrs); + + $this->element('label', array('for' => 'design_background-image_on', + 'class' => 'radio'), + _('On')); + + $attrs = array('name' => 'design_background-image_onoff', + 'type' => 'radio', + 'id' => 'design_background-image_off', + 'class' => 'radio', + 'value' => 'off'); + + if ($design->disposition & BACKGROUND_OFF) { + $attrs['checked'] = 'checked'; + } + + $this->element('input', $attrs); + + $this->element('label', array('for' => 'design_background-image_off', + 'class' => 'radio'), + _('Off')); + $this->element('p', 'form_guide', _('Turn background image on or off.')); + $this->elementEnd('li'); + } + + $this->elementStart('li'); + $this->checkbox('design_background-image_repeat', + _('Tile background image'), + ($design->disposition & BACKGROUND_TILE) ? true : false ); + $this->elementEnd('li'); + $this->elementEnd('ul'); $this->elementEnd('fieldset'); @@ -100,61 +159,93 @@ class DesignsettingsAction extends AccountSettingsAction $this->element('legend', null, _('Change colours')); $this->elementStart('ul', 'form_data'); - //This is a JSON object in the DB field. Here for testing. Remove later. - $userSwatch = '{"body":{"background-color":"#F0F2F5"}, - "#content":{"background-color":"#FFFFFF"}, - "#aside_primary":{"background-color":"#CEE1E9"}, - "html body":{"color":"#000000"}, - "a":{"color":"#002E6E"}}'; + try { - //Default theme swatch -- Where should this be stored? - $defaultSwatch = array('body' => array('background-color' => '#F0F2F5'), - '#content' => array('background-color' => '#FFFFFF'), - '#aside_primary' => array('background-color' => '#CEE1E9'), - 'html body' => array('color' => '#000000'), - 'a' => array('color' => '#002E6E')); + $bgcolor = new WebColor($design->backgroundcolor); - $userSwatch = ($userSwatch) ? json_decode($userSwatch, true) : $defaultSwatch; - - $s = 0; - $labelSwatch = array('Background', - 'Content', - 'Sidebar', - 'Text', - 'Links'); - foreach($userSwatch as $propertyvalue => $value) { - $foo = array_values($value); $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-'.$s), _($labelSwatch[$s])); - $this->element('input', array('name' => 'swatch-'.$s, //prefer swatch[$s] ? + $this->element('label', array('for' => 'swatch-1'), _('Background')); + $this->element('input', array('name' => 'design_background', 'type' => 'text', - 'id' => 'swatch-'.$s, + 'id' => 'swatch-1', 'class' => 'swatch', 'maxlength' => '7', 'size' => '7', - 'value' => $foo[0])); + 'value' => '#' . $bgcolor->hexValue())); $this->elementEnd('li'); - $s++; - } - $this->elementEnd('ul'); - $this->elementEnd('fieldset'); + $ccolor = new WebColor($design->contentcolor); - $this->element('input', array('id' => 'settings_design_reset', - 'type' => 'reset', - 'value' => 'Reset', - 'class' => 'submit form_action-primary', - 'title' => _('Reset back to default'))); - $this->submit('save', _('Save'), 'submit form_action-secondary', 'save', _('Save design')); + $this->elementStart('li'); + $this->element('label', array('for' => 'swatch-2'), _('Content')); + $this->element('input', array('name' => 'design_content', + 'type' => 'text', + 'id' => 'swatch-2', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '#' . $ccolor->hexValue())); + $this->elementEnd('li'); + + $sbcolor = new WebColor($design->sidebarcolor); + + $this->elementStart('li'); + $this->element('label', array('for' => 'swatch-3'), _('Sidebar')); + $this->element('input', array('name' => 'design_sidebar', + 'type' => 'text', + 'id' => 'swatch-3', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '#' . $sbcolor->hexValue())); + $this->elementEnd('li'); + + $tcolor = new WebColor($design->textcolor); + + $this->elementStart('li'); + $this->element('label', array('for' => 'swatch-4'), _('Text')); + $this->element('input', array('name' => 'design_text', + 'type' => 'text', + 'id' => 'swatch-4', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '#' . $tcolor->hexValue())); + $this->elementEnd('li'); + + $lcolor = new WebColor($design->linkcolor); + + $this->elementStart('li'); + $this->element('label', array('for' => 'swatch-5'), _('Links')); + $this->element('input', array('name' => 'design_links', + 'type' => 'text', + 'id' => 'swatch-5', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '#' . $lcolor->hexValue())); + + $this->elementEnd('li'); + + } catch (WebColorException $e) { + common_log(LOG_ERR, 'Bad color values in design ID: ' . + $design->id); + } + + $this->elementEnd('ul'); + $this->elementEnd('fieldset'); + + $this->element('input', array('id' => 'settings_design_reset', + 'type' => 'reset', + 'value' => 'Reset', + 'class' => 'submit form_action-primary', + 'title' => _('Reset back to default'))); + + $this->submit('save', _('Save'), 'submit form_action-secondary', + 'save', _('Save design')); -/*TODO: Check submitted form values: -json_encode(form values) -if submitted Swatch == DefaultSwatch, don't store in DB. -else store in BD -*/ $this->elementEnd('fieldset'); $this->elementEnd('form'); - } /** @@ -168,63 +259,37 @@ else store in BD function handlePost() { - /* - // CSRF protection + // XXX: Robin's workaround for a bug in PHP where $_POST + // and $_FILE are empty in the case that the uploaded + // file is bigger than PHP is configured to handle. - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) { - $user = common_current_user(); - assert(!is_null($user)); // should already be checked + $msg = _('The server was unable to handle that much POST ' . + 'data (%s bytes) due to its current configuration.'); - // FIXME: scrub input - - $newpassword = $this->arg('newpassword'); - $confirm = $this->arg('confirm'); - - # Some validation - - if (strlen($newpassword) < 6) { - $this->showForm(_('Password must be 6 or more characters.')); - return; - } else if (0 != strcmp($newpassword, $confirm)) { - $this->showForm(_('Passwords don\'t match.')); - return; - } - - if ($user->password) { - $oldpassword = $this->arg('oldpassword'); - - if (!common_check_user($user->nickname, $oldpassword)) { - $this->showForm(_('Incorrect old password')); - return; + $this->showForm(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); } } - $original = clone($user); - - $user->password = common_munge_password($newpassword, $user->id); - - $val = $user->validate(); - if ($val !== true) { - $this->showForm(_('Error saving user; invalid.')); + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); return; } - if (!$user->update($original)) { - $this->serverError(_('Can\'t save new password.')); - return; + if ($this->arg('save')) { + $this->saveDesign(); + } else if ($this->arg('reset')) { + $this->resetDesign(); + } else { + $this->showForm(_('Unexpected form submission.')); } - - $this->showForm(_('Password saved.'), true); - */ } - /** * Add the Farbtastic stylesheet * @@ -254,11 +319,199 @@ else store in BD parent::showScripts(); $farbtasticPack = common_path('js/farbtastic/farbtastic.js'); - $farbtasticGo = common_path('js/farbtastic/farbtastic.go.js'); + $userDesignGo = common_path('js/userdesign.go.js'); $this->element('script', array('type' => 'text/javascript', 'src' => $farbtasticPack)); $this->element('script', array('type' => 'text/javascript', - 'src' => $farbtasticGo)); + 'src' => $userDesignGo)); } + + /** + * Get a default user design + * + * @return Design design + */ + + function defaultDesign() + { + $defaults = common_config('site', 'design'); + + $design = new Design(); + + try { + + $color = new WebColor(); + + $color->parseColor($defaults['backgroundcolor']); + $design->backgroundcolor = $color->intValue(); + + $color->parseColor($defaults['contentcolor']); + $design->contentcolor = $color->intValue(); + + $color->parseColor($defaults['sidebarcolor']); + $design->sidebarcolor = $color->intValue(); + + $color->parseColor($defaults['textcolor']); + $design->textcolor = $color->intValue(); + + $color->parseColor($defaults['linkcolor']); + $design->linkcolor = $color->intValue(); + + $design->backgroundimage = $defaults['backgroundimage']; + + $design->disposition = $defaults['disposition']; + + } catch (WebColorException $e) { + common_log(LOG_ERR, _('Bad default color settings: ' . + $e->getMessage())); + } + + return $design; + } + + /** + * Save or update the user's design settings + * + * @return void + */ + + function saveDesign() + { + try { + + $bgcolor = new WebColor($this->trimmed('design_background')); + $ccolor = new WebColor($this->trimmed('design_content')); + $sbcolor = new WebColor($this->trimmed('design_sidebar')); + $tcolor = new WebColor($this->trimmed('design_text')); + $lcolor = new WebColor($this->trimmed('design_links')); + + } catch (WebColorException $e) { + $this->showForm($e->getMessage()); + return; + } + + $onoff = $this->arg('design_background-image_onoff'); + + $on = false; + $off = false; + $tile = false; + + if ($onoff == 'on') { + $on = true; + } else { + $off = true; + } + + $repeat = $this->boolean('design_background-image_repeat'); + + if ($repeat) { + $tile = true; + } + + $user = common_current_user(); + $design = $user->getDesign(); + + if (!empty($design)) { + + $original = clone($design); + + $design->backgroundcolor = $bgcolor->intValue(); + $design->contentcolor = $ccolor->intValue(); + $design->sidebarcolor = $sbcolor->intValue(); + $design->textcolor = $tcolor->intValue(); + $design->linkcolor = $lcolor->intValue(); + $design->backgroundimage = $filepath; + + $design->setDisposition($on, $off, $tile); + + $result = $design->update($original); + + if ($result === false) { + common_log_db_error($design, 'UPDATE', __FILE__); + $this->showForm(_('Couldn\'t update your design.')); + return; + } + + // update design + } else { + + $user->query('BEGIN'); + + // save new design + $design = new Design(); + + $design->backgroundcolor = $bgcolor->intValue(); + $design->contentcolor = $ccolor->intValue(); + $design->sidebarcolor = $sbcolor->intValue(); + $design->textcolor = $tcolor->intValue(); + $design->linkcolor = $lcolor->intValue(); + $design->backgroundimage = $filepath; + + $design->setDisposition($on, $off, $tile); + + $id = $design->insert(); + + if (empty($id)) { + common_log_db_error($id, 'INSERT', __FILE__); + $this->showForm(_('Unable to save your design settings!')); + return; + } + + $original = clone($user); + $user->design_id = $id; + $result = $user->update($original); + + if (empty($result)) { + common_log_db_error($original, 'UPDATE', __FILE__); + $this->showForm(_('Unable to save your design settings!')); + $user->query('ROLLBACK'); + return; + } + + $user->query('COMMIT'); + + } + + // Now that we have a Design ID we can add a file to the design. + // XXX: This is an additional DB hit, but figured having the image + // associated with the Design rather than the User was worth + // it. -- Zach + + if ($_FILES['design_background-image_file']['error'] == + UPLOAD_ERR_OK) { + + $filepath = null; + + try { + $imagefile = + ImageFile::fromUpload('design_background-image_file'); + } catch (Exception $e) { + $this->showForm($e->getMessage()); + return; + } + + $filename = Design::filename($design->id, + image_type_to_extension($imagefile->type), + common_timestamp()); + + $filepath = Design::path($filename); + + move_uploaded_file($imagefile->filepath, $filepath); + + $original = clone($design); + $design->backgroundimage = $filename; + $design->setDisposition(true, false, false); + $result = $design->update($original); + + if ($result === false) { + common_log_db_error($design, 'UPDATE', __FILE__); + $this->showForm(_('Couldn\'t update your design.')); + return; + } + } + + $this->showForm(_('Design preferences saved.'), true); + } + } diff --git a/actions/featured.php b/actions/featured.php index 79eba2aa67..04365687d7 100644 --- a/actions/featured.php +++ b/actions/featured.php @@ -32,7 +32,7 @@ if (!defined('LACONICA')) { exit(1); } -require_once(INSTALLDIR.'/lib/profilelist.php'); +require_once INSTALLDIR.'/lib/profilelist.php'; require_once INSTALLDIR.'/lib/publicgroupnav.php'; /** @@ -107,7 +107,6 @@ class FeaturedAction extends Action $featured_nicks = common_config('nickname', 'featured'); - if (count($featured_nicks) > 0) { $quoted = array(); @@ -136,7 +135,7 @@ class FeaturedAction extends Action $cnt = $profile->find(); if ($cnt > 0) { - $featured = new ProfileList($profile, null, $this); + $featured = new ProfileList($profile, $this); $featured->show(); } diff --git a/actions/groupblock.php b/actions/groupblock.php index 93662da799..28685b1d56 100644 --- a/actions/groupblock.php +++ b/actions/groupblock.php @@ -151,17 +151,19 @@ class GroupblockAction extends Action function areYouSureForm() { $id = $this->profile->id; + $this->elementStart('form', array('id' => 'block-' . $id, + 'method' => 'post', + 'class' => 'form_settings form_entity_block', + 'action' => common_local_url('groupblock'))); + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->element('legend', null, _('Block user')); $this->element('p', null, sprintf(_('Are you sure you want to block user "%s" from the group "%s"? '. 'They will be removed from the group, unable to post, and '. 'unable to subscribe to the group in the future.'), $this->profile->getBestName(), $this->group->getBestName())); - $this->elementStart('form', array('id' => 'block-' . $id, - 'method' => 'post', - 'class' => 'block', - 'action' => common_local_url('groupblock'))); - $this->hidden('token', common_session_token()); $this->hidden('blockto-' . $this->profile->id, $this->profile->id, 'blockto'); @@ -173,8 +175,9 @@ class GroupblockAction extends Action $this->hidden($k, $v); } } - $this->submit('no', _('No')); - $this->submit('yes', _('Yes')); + $this->submit('form_action-no', _('No'), 'submit form_action-primary', 'no', _("Do not block this user from this group")); + $this->submit('form_action-yes', _('Yes'), 'submit form_action-secondary', 'yes', _('Block this user from this group')); + $this->elementEnd('fieldset'); $this->elementEnd('form'); } diff --git a/actions/invite.php b/actions/invite.php index 7e52cdbcc6..c793f58249 100644 --- a/actions/invite.php +++ b/actions/invite.php @@ -19,7 +19,7 @@ if (!defined('LACONICA')) { exit(1); } -class InviteAction extends Action +class InviteAction extends CurrentUserDesignAction { var $mode = null; var $error = null; diff --git a/actions/newnotice.php b/actions/newnotice.php index 02976a2ae2..72ccd8c325 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -231,7 +231,6 @@ class NewnoticeAction extends Action if (isset($mimetype)) { $this->storeFile($notice, $mimetype); } - $this->saveUrls($notice); common_broadcast_notice($notice); if ($this->boolean('ajax')) { @@ -284,24 +283,6 @@ class NewnoticeAction extends Action } } - /** save all urls in the notice to the db - * - * follow redirects and save all available file information - * (mimetype, date, size, oembed, etc.) - * - * @param class $notice Notice to pull URLs from - * - * @return void - */ - function saveUrls($notice, $uploaded = null) { - common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id); - } - - function saveUrl($data) { - list($url, $notice_id) = $data; - $zzz = File::processNew($url, $notice_id); - } - /** * Show an Ajax-y error message * diff --git a/actions/peopletag.php b/actions/peopletag.php index 5add754858..dd3c1c0899 100644 --- a/actions/peopletag.php +++ b/actions/peopletag.php @@ -124,7 +124,7 @@ class PeopletagAction extends Action $profile->query(sprintf($qry, $this->tag, $lim)); - $pl = new ProfileList($profile, null, $this); + $pl = new ProfileList($profile, $this); $cnt = $pl->show(); $this->pagination($this->page > 1, diff --git a/actions/replies.php b/actions/replies.php index eac4d0a3ae..d7ed440e92 100644 --- a/actions/replies.php +++ b/actions/replies.php @@ -45,9 +45,8 @@ require_once INSTALLDIR.'/lib/feedlist.php'; * @link http://laconi.ca/ */ -class RepliesAction extends Action +class RepliesAction extends OwnerDesignAction { - var $user = null; var $page = null; /** diff --git a/actions/showfavorites.php b/actions/showfavorites.php index 865045337a..01f38a8927 100644 --- a/actions/showfavorites.php +++ b/actions/showfavorites.php @@ -45,7 +45,7 @@ require_once INSTALLDIR.'/lib/feedlist.php'; * @link http://laconi.ca/ */ -class ShowfavoritesAction extends Action +class ShowfavoritesAction extends CurrentUserDesignAction { /** User we're getting the faves of */ var $user = null; diff --git a/actions/showstream.php b/actions/showstream.php index 72316b2592..cd5d4bb701 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -370,7 +370,7 @@ class ShowstreamAction extends ProfileAction { $notice = empty($this->tag) ? $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1) - : $this->user->getTaggedNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1, 0, 0, null, $this->tag); + : $this->user->getTaggedNotices($this->tag, ($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1, 0, 0, null); $pnl = new ProfileNoticeList($notice, $this); $cnt = $pnl->show(); diff --git a/actions/twitapifavorites.php b/actions/twitapifavorites.php index 8656adbe86..2266ba11c2 100644 --- a/actions/twitapifavorites.php +++ b/actions/twitapifavorites.php @@ -34,6 +34,11 @@ class TwitapifavoritesAction extends TwitterapiAction $user = $this->get_user($apidata['api_arg'], $apidata); if (empty($user)) { + if ($apidata['content-type'] == 'xml') { + $this->show_single_xml_status($notice); + } elseif ($apidata['content-type'] == 'json') { + $this->show_single_json_status($notice); + } $this->clientError('Not Found', 404, $apidata['content-type']); return; } @@ -91,7 +96,6 @@ class TwitapifavoritesAction extends TwitterapiAction // Check for RESTfulness if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { - // XXX: Twitter just prints the err msg, no XML / JSON. $this->clientError(_('This method requires a POST or DELETE.'), 400, $apidata['content-type']); return; @@ -102,10 +106,9 @@ class TwitapifavoritesAction extends TwitterapiAction return; } - $user = $apidata['user']; // Always the auth user - + $user = $apidata['user']; // Always the auth user $notice_id = $apidata['api_arg']; - $notice = Notice::staticGet($notice_id); + $notice = Notice::staticGet($notice_id); if (empty($notice)) { $this->clientError(_('No status found with that ID.'), @@ -115,7 +118,7 @@ class TwitapifavoritesAction extends TwitterapiAction // XXX: Twitter lets you fave things repeatedly via api. if ($user->hasFave($notice)) { - $this->clientError(_('This notice is already a favorite!'), + $this->clientError(_('This status is already a favorite!'), 403, $apidata['content-type']); return; } @@ -123,7 +126,7 @@ class TwitapifavoritesAction extends TwitterapiAction $fave = Fave::addNew($user, $notice); if (empty($fave)) { - $this->serverError(_('Could not create favorite.')); + $this->clientError(_('Could not create favorite.')); return; } @@ -141,7 +144,55 @@ class TwitapifavoritesAction extends TwitterapiAction function destroy($args, $apidata) { parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); + + // Check for RESTfulness + if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { + $this->clientError(_('This method requires a POST or DELETE.'), + 400, $apidata['content-type']); + return; + } + + if (!in_array($apidata['content-type'], array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + $user = $apidata['user']; // Always the auth user + $notice_id = $apidata['api_arg']; + $notice = Notice::staticGet($notice_id); + + if (empty($notice)) { + $this->clientError(_('No status found with that ID.'), + 404, $apidata['content-type']); + return; + } + + $fave = new Fave(); + $fave->user_id = $this->id; + $fave->notice_id = $notice->id; + + if (!$fave->find(true)) { + $this->clientError(_('That status is not a favorite!'), + 403, $apidata['content-type']); + return; + } + + $result = $fave->delete(); + + if (!$result) { + common_log_db_error($fave, 'DELETE', __FILE__); + $this->clientError(_('Could not delete favorite.'), 404); + return; + } + + $user->blowFavesCache(); + + if ($apidata['content-type'] == 'xml') { + $this->show_single_xml_status($notice); + } elseif ($apidata['content-type'] == 'json') { + $this->show_single_json_status($notice); + } + } // XXX: these two funcs swiped from faves. diff --git a/actions/usergroups.php b/actions/usergroups.php index e3088dcbd8..7ead6e6e49 100644 --- a/actions/usergroups.php +++ b/actions/usergroups.php @@ -46,9 +46,8 @@ require_once INSTALLDIR.'/lib/grouplist.php'; * @link http://laconi.ca/ */ -class UsergroupsAction extends Action +class UsergroupsAction extends OwnerDesignAction { - var $user = null; var $page = null; var $profile = null; diff --git a/background/.gitignore b/background/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/classes/Design.php b/classes/Design.php new file mode 100644 index 0000000000..da4b670be0 --- /dev/null +++ b/classes/Design.php @@ -0,0 +1,155 @@ +. + */ + +if (!defined('LACONICA')) { + exit(1); +} + +define('BACKGROUND_ON', 1); +define('BACKGROUND_OFF', 2); +define('BACKGROUND_TILE', 4); + +/** + * Table Definition for design + */ + +require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; +require_once INSTALLDIR . '/lib/webcolor.php'; + +class Design extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'design'; // table name + public $id; // int(4) primary_key not_null + public $backgroundcolor; // int(4) + public $contentcolor; // int(4) + public $sidebarcolor; // int(4) + public $textcolor; // int(4) + public $linkcolor; // int(4) + public $backgroundimage; // varchar(255) + public $disposition; // tinyint(1) default_1 + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Design',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function showCSS($out) + { + try { + + $bgcolor = new WebColor($this->backgroundcolor); + $ccolor = new WebColor($this->contentcolor); + $sbcolor = new WebColor($this->sidebarcolor); + $tcolor = new WebColor($this->textcolor); + $lcolor = new WebColor($this->linkcolor); + + } catch (WebColorException $e) { + // This shouldn't happen + common_log(LOG_ERR, "Unable to create color for design $id.", + __FILE__); + } + + $css = 'body { background-color: #' . $bgcolor->hexValue() . ' }' . "\n"; + $css .= '#content, #site_nav_local_views .current a { background-color: #'; + $css .= $ccolor->hexValue() . '} '."\n"; + $css .= '#aside_primary { background-color: #'. $sbcolor->hexValue() . ' }' . "\n"; + $css .= 'html body { color: #'. $tcolor->hexValue() . ' }'. "\n"; + $css .= 'a { color: #' . $lcolor->hexValue() . ' }' . "\n"; + + if (!empty($this->backgroundimage) && + $this->disposition & BACKGROUND_ON) { + + $repeat = ($this->disposition & BACKGROUND_TILE) ? + 'background-repeat:repeat;' : + 'background-repeat:no-repeat;'; + + $css .= 'body { background-image:url(' . + Design::url($this->backgroundimage) . + '); ' . $repeat . ' }' . "\n"; + } + + $out->element('style', array('type' => 'text/css'), $css); + + } + + static function filename($id, $extension, $extra=null) + { + return $id . (($extra) ? ('-' . $extra) : '') . $extension; + } + + static function path($filename) + { + $dir = common_config('background', 'dir'); + + if ($dir[strlen($dir)-1] != '/') { + $dir .= '/'; + } + + return $dir . $filename; + } + + static function url($filename) + { + $path = common_config('background', 'path'); + + if ($path[strlen($path)-1] != '/') { + $path .= '/'; + } + + if ($path[0] != '/') { + $path = '/'.$path; + } + + $server = common_config('background', 'server'); + + if (empty($server)) { + $server = common_config('site', 'server'); + } + + // XXX: protocol + + return 'http://'.$server.$path.$filename; + } + + function setDisposition($on, $off, $tile) + { + if ($on) { + $this->disposition |= BACKGROUND_ON; + } else { + $this->disposition &= ~BACKGROUND_ON; + } + + if ($off) { + $this->disposition |= BACKGROUND_OFF; + } else { + $this->disposition &= ~BACKGROUND_OFF; + } + + if ($tile) { + $this->disposition |= BACKGROUND_TILE; + } else { + $this->disposition &= ~BACKGROUND_TILE; + } + } + +} diff --git a/classes/File.php b/classes/File.php index 24ab11b8eb..08320faf8f 100644 --- a/classes/File.php +++ b/classes/File.php @@ -79,7 +79,6 @@ class File extends Memcached_DataObject && ('text/html' === substr($redir_data['type'], 0, 9)) && ($oembed_data = File_oembed::_getOembed($given_url)) && isset($oembed_data['json'])) { - File_oembed::saveNew($oembed_data['json'], $file_id); } return $x; @@ -98,7 +97,6 @@ class File extends Memcached_DataObject if ($redir_url === $given_url) { $x = File::saveNew($redir_data, $given_url); $file_id = $x->id; - } else { $x = File::processNew($redir_url, $notice_id); $file_id = $x->id; diff --git a/classes/File_oembed.php b/classes/File_oembed.php index f1b2cb13c0..6bf972f8fe 100644 --- a/classes/File_oembed.php +++ b/classes/File_oembed.php @@ -53,7 +53,7 @@ class File_oembed extends Memcached_DataObject function _getOembed($url, $maxwidth = 500, $maxheight = 400, $format = 'json') { - $cmd = 'http://oohembed.com/oohembed/?url=' . urlencode($url); + $cmd = common_config('oohembed', 'endpoint') . '?url=' . urlencode($url); if (is_int($maxwidth)) $cmd .= "&maxwidth=$maxwidth"; if (is_int($maxheight)) $cmd .= "&maxheight=$maxheight"; if (is_string($format)) $cmd .= "&format=$format"; diff --git a/classes/Notice.php b/classes/Notice.php index 333832d0b1..e621805dfc 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -222,6 +222,13 @@ class Notice extends Memcached_DataObject $notice->addToInboxes(); $notice->saveGroups(); + $notice->saveUrls(); + $orig2 = clone($notice); + $notice->rendered = common_render_content($final, $notice); + if (!$notice->update($orig2)) { + common_log_db_error($notice, 'UPDATE', __FILE__); + return _('Problem saving notice.'); + } $notice->query('COMMIT'); @@ -236,6 +243,22 @@ class Notice extends Memcached_DataObject return $notice; } + /** save all urls in the notice to the db + * + * follow redirects and save all available file information + * (mimetype, date, size, oembed, etc.) + * + * @return void + */ + function saveUrls() { + common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id); + } + + function saveUrl($data) { + list($url, $notice_id) = $data; + File::processNew($url, $notice_id); + } + static function checkDupes($profile_id, $content) { $profile = Profile::staticGet($profile_id); if (!$profile) { @@ -356,6 +379,12 @@ class Notice extends Memcached_DataObject if ($tag->find()) { while ($tag->fetch()) { $tag->blowCache($blowLast); + $ck = 'profile:notice_ids_tagged:' . $this->profile_id . ':' . $tag->tag; + + $cache->delete($ck); + if ($blowLast) { + $cache->delete($ck . ';last'); + } } } $tag->free(); diff --git a/classes/Profile.php b/classes/Profile.php index 4a459b9740..2f432ae8ed 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -153,18 +153,16 @@ class Profile extends Memcached_DataObject return null; } - function getTaggedNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null, $tag=null) + function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) { - // XXX: I'm not sure this is going to be any faster. It probably isn't. $ids = Notice::stream(array($this, '_streamTaggedDirect'), - array(), - 'profile:notice_ids:' . $this->id, - $offset, $limit, $since_id, $before_id, $since, $tag); - common_debug(print_r($ids, true)); + array($tag), + 'profile:notice_ids_tagged:' . $this->id . ':' . $tag, + $offset, $limit, $since_id, $max_id, $since); return Notice::getStreamByIds($ids); } - function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) + function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null) { // XXX: I'm not sure this is going to be any faster. It probably isn't. $ids = Notice::stream(array($this, '_streamDirect'), @@ -175,18 +173,23 @@ class Profile extends Memcached_DataObject return Notice::getStreamByIds($ids); } - function _streamTaggedDirect($offset, $limit, $since_id, $before_id, $since=null, $tag=null) + function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id, $since) { - common_debug('_streamTaggedDirect()'); + // XXX It would be nice to do this without a join + $notice = new Notice(); - $notice->profile_id = $this->id; - $query = "select id from notice join notice_tag on id=notice_id where tag='" . $notice->escape($tag) . "' and profile_id=" . $notice->escape($notice->profile_id); + + $query = + "select id from notice join notice_tag on id=notice_id where tag='". + $notice->escape($tag) . + "' and profile_id=" . $notice->escape($this->id); + if ($since_id != 0) { $query .= " and id > $since_id"; } - if ($before_id != 0) { - $query .= " and id < $before_id"; + if ($max_id != 0) { + $query .= " and id < $max_id"; } if (!is_null($since)) { @@ -198,21 +201,19 @@ class Profile extends Memcached_DataObject if (!is_null($offset)) { $query .= " limit $offset, $limit"; } + $notice->query($query); + $ids = array(); while ($notice->fetch()) { - common_debug(print_r($notice, true)); $ids[] = $notice->id; } return $ids; } - - - - function _streamDirect($offset, $limit, $since_id, $before_id, $since = null) + function _streamDirect($offset, $limit, $since_id, $max_id, $since = null) { $notice = new Notice(); diff --git a/classes/User.php b/classes/User.php index 08a166d5ae..c7eede94e1 100644 --- a/classes/User.php +++ b/classes/User.php @@ -62,14 +62,13 @@ class User extends Memcached_DataObject public $autosubscribe; // tinyint(1) public $urlshorteningservice; // varchar(50) default_ur1.ca public $inboxed; // tinyint(1) + public $design_id; // int(4) + public $viewdesigns; // tinyint(1) default_1 public $created; // datetime() not_null public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) - { - return Memcached_DataObject::staticGet('User',$k,$v); - } + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE @@ -684,4 +683,9 @@ class User extends Memcached_DataObject return ($cnt > 0); } + + function getDesign() + { + return Design::staticGet('id', $this->design_id); + } } diff --git a/classes/laconica.ini b/classes/laconica.ini old mode 100644 new mode 100755 index df292bbffd..1a650aba55 --- a/classes/laconica.ini +++ b/classes/laconica.ini @@ -38,6 +38,19 @@ modified = 384 [consumer__keys] consumer_key = K +[design] +id = 129 +backgroundcolor = 1 +contentcolor = 1 +sidebarcolor = 1 +textcolor = 1 +linkcolor = 1 +backgroundimage = 2 +disposition = 17 + +[design__keys] +id = N + [fave] notice_id = 129 user_id = 129 @@ -430,6 +443,8 @@ uri = 2 autosubscribe = 17 urlshorteningservice = 2 inboxed = 17 +design_id = 1 +viewdesigns = 17 created = 142 modified = 384 diff --git a/config.php.sample b/config.php.sample index 636f4cf8e2..7649c52623 100644 --- a/config.php.sample +++ b/config.php.sample @@ -222,4 +222,6 @@ $config['sphinx']['port'] = 3312; // $config['attachments']['file_quota'] = 5000000; // $config['attachments']['user_quota'] = 50000000; // $config['attachments']['monthly_quota'] = 15000000; +// $config['attachments']['uploads'] = true; +// $config['oohembed']['endpoint'] = 'http://oohembed.com/oohembed/'; diff --git a/db/laconica.sql b/db/laconica.sql index b8c0824f5a..b018afec8e 100644 --- a/db/laconica.sql +++ b/db/laconica.sql @@ -41,6 +41,7 @@ create table sms_carrier ( /* local users */ create table user ( + id integer primary key comment 'foreign key to profile table' references profile (id), nickname varchar(64) unique key comment 'nickname or username, duped in profile', password varchar(255) comment 'salted password, can be null for OpenID users', @@ -69,6 +70,9 @@ create table user ( autosubscribe tinyint default 0 comment 'automatically subscribe to users who subscribe to us', urlshorteningservice varchar(50) default 'ur1.ca' comment 'service to use for auto-shortening URLs', inboxed tinyint default 0 comment 'has an inbox been created for this user?', + design_id integer comment 'id of a design' references design(id), + viewdesigns tinyint default 1 comment 'whether to view user-provided designs', + created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified', @@ -484,6 +488,17 @@ create table file_to_post ( unique(file_id, post_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; +create table design ( + id integer primary key auto_increment comment 'design ID', + backgroundcolor integer comment 'main background color', + contentcolor integer comment 'content area background color', + sidebarcolor integer comment 'sidebar background color', + textcolor integer comment 'text color', + linkcolor integer comment 'link color', + backgroundimage varchar(255) comment 'background image, if any', + disposition tinyint default 1 comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image' +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + create table group_block ( group_id integer not null comment 'group profile is blocked from' references user_group (id), blocked integer not null comment 'profile that is blocked' references profile (id), diff --git a/js/farbtastic/farbtastic.js b/js/farbtastic/farbtastic.js index 24a377803c..d8b5ad9cdf 100644 --- a/js/farbtastic/farbtastic.js +++ b/js/farbtastic/farbtastic.js @@ -1,5 +1,21 @@ -// $Id: farbtastic.js,v 1.2 2007/01/08 22:53:01 unconed Exp $ -// Farbtastic 1.2 +/** + * Farbtastic Color Picker 1.2 + * © 2008 Steven Wittens + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ jQuery.fn.farbtastic = function (callback) { $.farbtastic(this, callback); diff --git a/js/farbtastic/farbtastic.go.js b/js/userdesign.go.js similarity index 66% rename from js/farbtastic/farbtastic.go.js rename to js/userdesign.go.js index 0149eca7d9..b54b492cce 100644 --- a/js/farbtastic/farbtastic.go.js +++ b/js/userdesign.go.js @@ -10,19 +10,19 @@ $(document).ready(function() { function UpdateColors(S) { C = $(S).val(); switch (parseInt(S.id.slice(-1))) { - case 0: default: - $('body').css({'background-color':C}); - break; - case 1: - $('#content').css({'background-color':C}); + case 1: default: + $('html, body').css({'background-color':C}); break; case 2: - $('#aside_primary').css({'background-color':C}); + $('#content, #site_nav_local_views .current a').css({'background-color':C}); break; case 3: - $('body').css({'color':C}); + $('#aside_primary').css({'background-color':C}); break; case 4: + $('html body').css({'color':C}); + break; + case 5: $('a').css({'color':C}); break; } @@ -49,7 +49,7 @@ $(document).ready(function() { } } - function Init() { + function InitFarbtastic() { $('#settings_design_color').append('
'); $('#color-picker').hide(); @@ -59,7 +59,9 @@ $(document).ready(function() { swatches .each(SynchColors) .blur(function() { - $(this).val($(this).val().toUpperCase()); + tv = $(this).val(); + $(this).val(tv.toUpperCase()); + (tv.length == 4) ? ((tv[0] == '#') ? $(this).val('#'+tv[1]+tv[1]+tv[2]+tv[2]+tv[3]+tv[3]) : '') : ''; }) .focus(function() { $('#color-picker').show(); @@ -73,13 +75,25 @@ $(document).ready(function() { } var f, swatches; - Init(); + InitFarbtastic(); $('#form_settings_design').bind('reset', function(){ setTimeout(function(){ swatches.each(function(){UpdateColors(this);}); $('#color-picker').remove(); swatches.unbind(); - Init(); + InitFarbtastic(); },10); }); -}); + + $('#design_background-image_off').focus(function() { + $('body').css({'background-image':'none'}); + }); + $('#design_background-image_on').focus(function() { + var bis = $('#design_background-image_onoff img')[0].src; + $('body').css({'background-image':'url('+bis+')'}); + }); + + $('#design_background-image_repeat').click(function() { + ($(this)[0].checked) ? $('body').css({'background-repeat':'repeat'}) : $('body').css({'background-repeat':'no-repeat'}); + }); +}); \ No newline at end of file diff --git a/js/util.js b/js/util.js index fd2500d447..17ae4c0719 100644 --- a/js/util.js +++ b/js/util.js @@ -230,23 +230,12 @@ $(document).ready(function(){ }; $("#form_notice").ajaxForm(PostNotice); $("#form_notice").each(addAjaxHidden); - NoticeHover(); NoticeReply(); NoticeAttachments(); }); - -function NoticeHover() { - function mouseHandler(e) { - $(e.target).closest('li.hentry')[(e.type === 'mouseover') ? 'addClass' : 'removeClass']('hover'); - }; - $('#content .notices').mouseover(mouseHandler); - $('#content .notices').mouseout(mouseHandler); -} - - function NoticeReply() { - if ($('#notice_data-text').length > 0) { + if ($('#notice_data-text').length > 0 && $('#content .notice_reply').length > 0) { $('#content .notice').each(function() { var notice = $(this)[0]; $($('.notice_reply', notice)[0]).click(function() { diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 45e4fa3196..e1726df287 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -46,7 +46,6 @@ if (!defined('LACONICA')) { * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ * @see Notice - * @see StreamAction * @see NoticeListItem * @see ProfileNoticeList */ diff --git a/lib/common.php b/lib/common.php index 51204cedea..eb8a2b8736 100644 --- a/lib/common.php +++ b/lib/common.php @@ -71,6 +71,15 @@ $config = array('name' => 'Just another Laconica microblog', 'server' => $_server, 'theme' => 'default', + 'skin' => 'default', + 'design' => + array('backgroundcolor' => '#F0F2F5', + 'contentcolor' => '#FFFFFF', + 'sidebarcolor' => '#CEE1E9', + 'textcolor' => '#000000', + 'linkcolor' => '#002E6E', + 'backgroundimage' => null, + 'disposition' => 1), 'path' => $_path, 'logfile' => null, 'logo' => null, @@ -111,6 +120,10 @@ $config = array('server' => null, 'dir' => INSTALLDIR . '/avatar/', 'path' => $_path . '/avatar/'), + 'background' => + array('server' => null, + 'dir' => INSTALLDIR . '/background/', + 'path' => $_path . '/background/'), 'public' => array('localonly' => true, 'blacklist' => array(), @@ -200,9 +213,11 @@ $config = 'file_quota' => 5000000, 'user_quota' => 50000000, 'monthly_quota' => 15000000, + 'uploads' => true, ), 'group' => array('maxaliases' => 3), + 'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/') ); $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); diff --git a/lib/personal.php b/lib/currentuserdesignaction.php similarity index 56% rename from lib/personal.php rename to lib/currentuserdesignaction.php index f92732375b..7c2520cf67 100644 --- a/lib/personal.php +++ b/lib/currentuserdesignaction.php @@ -2,7 +2,7 @@ /** * Laconica, the distributed open-source microblogging tool * - * User profile page + * Base class for actions that use the current user's design * * PHP version 5 * @@ -19,11 +19,10 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Personal + * @category Action * @package Laconica * @author Evan Prodromou - * @author Sarven Capadisli - * @copyright 2008-2009 Control Yourself, Inc. + * @copyright 2009 Control Yourself, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ */ @@ -33,28 +32,57 @@ if (!defined('LACONICA')) { } /** - * Base class for user profile page + * Base class for actions that use the current user's design * - * @category Personal + * Some pages (settings in particular) use the current user's chosen + * design. This superclass returns that design. + * + * @category Action * @package Laconica * @author Evan Prodromou * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ + * */ -class PersonalAction extends Action +class CurrentUserDesignAction extends Action { - var $user = null; + /** + * Show the user's design stylesheet + * + * @return nothing + */ + function showStylesheets() + { + parent::showStylesheets(); - function isReadOnly($args) + $design = $this->getDesign(); + + if (!empty($design)) { + $design->showCSS($this); + } + } + + /** + * A design for this action + * + * if the user attribute has been set, returns that user's + * design. + * + * @return Design a design object to use + */ + + function getDesign() { - return true; + $cur = common_current_user(); + + if (empty($cur)) { + return null; + } + + return $cur->getDesign(); } - function handle($args) - { - parent::handle($args); - } } diff --git a/lib/galleryaction.php b/lib/galleryaction.php index 8fa11a7562..498c828514 100644 --- a/lib/galleryaction.php +++ b/lib/galleryaction.php @@ -27,10 +27,9 @@ require_once INSTALLDIR.'/lib/profilelist.php'; define('AVATARS_PER_PAGE', 80); -class GalleryAction extends Action +class GalleryAction extends OwnerDesignAction { var $profile = null; - var $user = null; var $page = null; var $tag = null; diff --git a/lib/grouptagcloudsection.php b/lib/grouptagcloudsection.php index 5d68af28bf..9b7a10f6b9 100644 --- a/lib/grouptagcloudsection.php +++ b/lib/grouptagcloudsection.php @@ -32,7 +32,7 @@ if (!defined('LACONICA')) { } /** - * Personal tag cloud section + * Group tag cloud section * * @category Widget * @package Laconica @@ -64,12 +64,27 @@ class GroupTagCloudSection extends TagCloudSection $weightexpr='sum(exp(-(now() - notice_tag.created) / %s))'; } + $names = $this->group->getAliases(); + + $names = array_merge(array($this->group->nickname), $names); + + // XXX This is dumb. + + $quoted = array(); + + foreach ($names as $name) { + $quoted[] = "\"$name\""; + } + + $namestring = implode(',', $quoted); + $qry = 'SELECT notice_tag.tag, '. $weightexpr . ' as weight ' . 'FROM notice_tag JOIN notice ' . 'ON notice_tag.notice_id = notice.id ' . 'JOIN group_inbox on group_inbox.notice_id = notice.id ' . 'WHERE group_inbox.group_id = %d ' . + 'AND notice_tag.tag not in (%s) '. 'GROUP BY notice_tag.tag ' . 'ORDER BY weight DESC '; @@ -85,9 +100,9 @@ class GroupTagCloudSection extends TagCloudSection $tag = Memcached_DataObject::cachedQuery('Notice_tag', sprintf($qry, common_config('tag', 'dropoff'), - $this->group->id), + $this->group->id, + $namestring), 3600); return $tag; } - } diff --git a/lib/mailbox.php b/lib/mailbox.php index 01bbf5721a..f1f6e98c19 100644 --- a/lib/mailbox.php +++ b/lib/mailbox.php @@ -31,8 +31,6 @@ if (!defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/personal.php'; - define('MESSAGES_PER_PAGE', 20); /** @@ -47,11 +45,11 @@ define('MESSAGES_PER_PAGE', 20); * @see OutboxAction */ -class MailboxAction extends PersonalAction +class MailboxAction extends CurrentUserDesignAction { var $page = null; - function prepare($args) + function prepare($args) { parent::prepare($args); @@ -265,12 +263,12 @@ class MailboxAction extends PersonalAction * Returns either the name (and link) of the API client that posted the notice, * or one of other other channels. * - * @param string $source the source of the message + * @param string $source the source of the message * * @return void */ - function showSource($source) + function showSource($source) { $source_name = _($source); switch ($source) { @@ -297,4 +295,17 @@ class MailboxAction extends PersonalAction return; } + /** + * Mailbox actions are read only + * + * @param array $args other arguments + * + * @return boolean + */ + + function isReadOnly($args) + { + return true; + } + } diff --git a/lib/noticeform.php b/lib/noticeform.php index 0ad3658566..a36b7f31f7 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -90,7 +90,9 @@ class NoticeForm extends Form $this->user = common_current_user(); } - $this->enctype = 'multipart/form-data'; + if (common_config('attachments', 'uploads')) { + $this->enctype = 'multipart/form-data'; + } } /** @@ -148,12 +150,14 @@ class NoticeForm extends Form $this->out->element('dd', array('id' => 'notice_text-count'), '140'); $this->out->elementEnd('dl'); - $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach')); - $this->out->element('input', array('id' => 'notice_data-attach', - 'type' => 'file', - 'name' => 'attach', - 'title' => _('Attach a file'))); - $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); + if (common_config('attachments', 'uploads')) { + $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); + $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach')); + $this->out->element('input', array('id' => 'notice_data-attach', + 'type' => 'file', + 'name' => 'attach', + 'title' => _('Attach a file'))); + } if ($this->action) { $this->out->hidden('notice_return-to', $this->action, 'returnto'); } diff --git a/lib/noticelist.php b/lib/noticelist.php index c312292aba..ad792441a3 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -50,7 +50,6 @@ require_once INSTALLDIR.'/lib/attachmentlist.php'; * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ * @see Notice - * @see StreamAction * @see NoticeListItem * @see ProfileNoticeList */ diff --git a/lib/ownerdesignaction.php b/lib/ownerdesignaction.php new file mode 100644 index 0000000000..424474f429 --- /dev/null +++ b/lib/ownerdesignaction.php @@ -0,0 +1,88 @@ +. + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +/** + * Base class for actions that use the page owner's design + * + * Some pages have a clear "owner" -- like the profile page, subscriptions + * pages, etc. This superclass uses that owner's chosen design for the page + * design. + * + * @category Action + * @package Laconica + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + */ + +class OwnerDesignAction extends Action { + + /** The user for this page. */ + + var $user = null; + + /** + * Show the owner's design stylesheet + * + * @return nothing + */ + function showStylesheets() + { + parent::showStylesheets(); + + $design = $this->getDesign(); + + if (!empty($design)) { + $design->showCSS($this); + } + } + + /** + * A design for this action + * + * if the user attribute has been set, returns that user's + * design. + * + * @return Design a design object to use + */ + + function getDesign() + { + if (empty($this->user)) { + return null; + } + + return $this->user->getDesign(); + } +} diff --git a/lib/profileaction.php b/lib/profileaction.php index 298f34b221..2519922b2b 100644 --- a/lib/profileaction.php +++ b/lib/profileaction.php @@ -47,9 +47,8 @@ require_once INSTALLDIR.'/lib/groupminilist.php'; * @link http://laconi.ca/ */ -class ProfileAction extends Action +class ProfileAction extends OwnerDesignAction { - var $user = null; var $page = null; var $profile = null; var $tag = null; diff --git a/lib/router.php b/lib/router.php index 0fbaba9ed5..8b6f636182 100644 --- a/lib/router.php +++ b/lib/router.php @@ -351,7 +351,8 @@ class Router $m->connect('api/favorites/:method/:argument', array('action' => 'api', - 'apiaction' => 'favorites')); + 'apiaction' => 'favorites', + array('method' => '(create|destroy)'))); $m->connect('api/favorites/:argument', array('action' => 'api', diff --git a/lib/settingsaction.php b/lib/settingsaction.php index db20c58043..17d3a2f64d 100644 --- a/lib/settingsaction.php +++ b/lib/settingsaction.php @@ -43,7 +43,7 @@ if (!defined('LACONICA')) { * @see Widget */ -class SettingsAction extends Action +class SettingsAction extends CurrentUserDesignAction { /** * A message for the user. diff --git a/lib/stream.php b/lib/stream.php deleted file mode 100644 index 0cb9e0bf4f..0000000000 --- a/lib/stream.php +++ /dev/null @@ -1,32 +0,0 @@ -. - */ - -if (!defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/lib/personal.php'); -require_once(INSTALLDIR.'/lib/noticelist.php'); - -class StreamAction extends PersonalAction -{ - function show_notice_list($notice) - { - $nl = new NoticeList($notice); - return $nl->show(); - } -} diff --git a/lib/twitterapi.php b/lib/twitterapi.php index 569bc6d7a2..269b60efc6 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -545,7 +545,7 @@ class TwitterapiAction extends Action $this->init_twitter_atom(); break; default: - $this->client_error(_('Not a supported data format.')); + $this->clientError(_('Not a supported data format.')); break; } @@ -573,13 +573,13 @@ class TwitterapiAction extends Action $this->end_twitter_rss(); break; default: - $this->client_error(_('Not a supported data format.')); + $this->clientError(_('Not a supported data format.')); break; } return; } - function client_error($msg, $code = 400, $content_type = 'json') + function clientError($msg, $code = 400, $content_type = 'json') { static $status = array(400 => 'Bad Request', @@ -666,7 +666,7 @@ class TwitterapiAction extends Action $this->show_json_objects($profile_array); break; default: - $this->client_error(_('Not a supported data format.')); + $this->clientError(_('Not a supported data format.')); return; } return; diff --git a/lib/webcolor.php b/lib/webcolor.php new file mode 100644 index 0000000000..f3ca6e94a8 --- /dev/null +++ b/lib/webcolor.php @@ -0,0 +1,192 @@ +. + * + * @category Personal + * @package Laconica + * @author Zach Copley + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +class WebColor { + + // XXX: Maybe make getters and setters for r,g,b values and tuples, + // e.g.: to support this kinda CSS representation: rgb(255,0,0) + // http://www.w3.org/TR/CSS21/syndata.html#color-units + + var $red = 0; + var $green = 0; + var $blue = 0; + + /** + * Constructor + * + * @return nothing + */ + + function __construct($color = null) + { + if (isset($color)) { + $this->parseColor($color); + } + } + + /** + * Parses input to and tries to determine whether the color + * is being specified via an integer or hex tuple and sets + * the RGB instance variables accordingly. + * + * XXX: Maybe support (r,g,b) style, and array? + * + * @param mixed $color + * + * @return nothing + */ + + function parseColor($color) { + + if (is_numeric($color)) { + $this->setIntColor($color); + } else { + + // XXX named colors + + // XXX: probably should do even more validation + + if (preg_match('/(#([0-9A-Fa-f]{3,6})\b)/u', $color) > 0) { + $this->setHexColor($color); + } else { + $errmsg = _('%s is not a valid color!'); + throw new WebColorException(sprintf($errmsg, $color)); + } + } + } + + /** + * @param string $name + * + * @return nothing + */ + + function setNamedColor($name) + { + // XXX Implement this + } + + + /** + * Sets the RGB color values from a a hex tuple + * + * @param string $hexcolor + * + * @return nothing + */ + + function setHexColor($hexcolor) { + + if ($hexcolor[0] == '#') { + $hexcolor = substr($hexcolor, 1); + } + + if (strlen($hexcolor) == 6) { + list($r, $g, $b) = array($hexcolor[0].$hexcolor[1], + $hexcolor[2].$hexcolor[3], + $hexcolor[4].$hexcolor[5]); + } elseif (strlen($hexcolor) == 3) { + list($r, $g, $b) = array($hexcolor[0].$hexcolor[0], + $hexcolor[1].$hexcolor[1], + $hexcolor[2].$hexcolor[2]); + } else { + $errmsg = _('%s is not a valid color! Use 3 or 6 hex chars.'); + throw new WebColorException(sprintf($errmsg, $hexcolor)); + } + + $this->red = hexdec($r); + $this->green = hexdec($g); + $this->blue = hexdec($b); + + } + + /** + * Sets the RGB color values from a 24-bit integer + * + * @param int $intcolor + * + * @return nothing + */ + + function setIntColor($intcolor) + { + // We could do 32 bit and have an alpha channel because + // Sarven wants one real bad, but nah. + + $this->red = $intcolor >> 16; + $this->green = $intcolor >> 8 & 0xFF; + $this->blue = $intcolor & 0xFF; + + } + + /** + * Returns a hex tuple of the RGB color useful for output in HTML + * + * @return string + */ + + function hexValue() { + + $hexcolor = (strlen(dechex($this->red)) < 2 ? '0' : '' ) . + dechex($this->red); + $hexcolor .= (strlen(dechex($this->green)) < 2 ? '0' : '') . + dechex($this->green); + $hexcolor .= (strlen(dechex($this->blue)) < 2 ? '0' : '') . + dechex($this->blue); + + return strtoupper($hexcolor); + + } + + /** + * Returns a 24-bit packed integer representation of the RGB color + * for convenient storage in the DB + * + * XXX: probably could just use hexdec() instead + * + * @return int + */ + + function intValue() + { + $intcolor = 256 * 256 * $this->red + 256 * $this->green + $this->blue; + return $intcolor; + } + +} + +class WebColorException extends Exception +{ +} + +?> \ No newline at end of file diff --git a/scripts/setup.cfg.sample b/scripts/setup.cfg.sample index 4194bc146d..450b9c30a3 100644 --- a/scripts/setup.cfg.sample +++ b/scripts/setup.cfg.sample @@ -2,6 +2,7 @@ # Base database name; full name will include nickname +export DBHOST=masterdb.example.net export DBBASE=_example_net export USERBASE=_example_net export ADMIN=root diff --git a/scripts/setup_status_network.sh b/scripts/setup_status_network.sh index d80612b940..e1d14593fb 100755 --- a/scripts/setup_status_network.sh +++ b/scripts/setup_status_network.sh @@ -11,13 +11,13 @@ export username=$nickname$USERBASE # Create the db -mysqladmin -u $ADMIN --password=$ADMINPASS create $database +mysqladmin -h $DBHOST -u $ADMIN --password=$ADMINPASS create $database for f in laconica.sql sms_carrier.sql foreign_services.sql notice_source.sql; do - mysql -u $ADMIN --password=$ADMINPASS $database < ../db/$f; + mysql -h $DBHOST -u $ADMIN --password=$ADMINPASS $database < ../db/$f; done -mysql -u $ADMIN --password=$ADMINPASS $SITEDB << ENDOFCOMMANDS +mysql -h $DBHOST -u $ADMIN --password=$ADMINPASS $SITEDB << ENDOFCOMMANDS GRANT INSERT,SELECT,UPDATE,DELETE ON $database.* TO '$username'@'localhost' IDENTIFIED BY '$password'; GRANT INSERT,SELECT,UPDATE,DELETE ON $database.* TO '$username'@'%' IDENTIFIED BY '$password'; diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 0cbd0d774a..d6ecef2fcc 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -12,9 +12,9 @@ img { display:block; border:0; } a abbr { cursor: pointer; border-bottom:0; } table { border-collapse:collapse; } ol { list-style-position:inside; } -html { font-size: 87.5%; background-color:#fff; height:100%; } +html { font-size: 87.5%; } body { -background-color:#fff; +background-color:#FFFFFF; color:#000; font-family:sans-serif; font-size:1em; @@ -77,7 +77,8 @@ margin:0 0 18px 0; form label { font-weight:bold; } -input.checkbox { +input.checkbox, +input.radio { position:relative; top:2px; left:0; @@ -154,7 +155,8 @@ font-weight:bold; #form_invite legend, #form_notice_delete legend, #form_password_recover legend, -#form_password_change legend { +#form_password_change legend, +.form_entity_block legend { display:none; } @@ -168,7 +170,8 @@ margin-bottom:0; margin-bottom:11px; } -.form_settings input.checkbox { +.form_settings input.checkbox, +.form_settings input.radio { margin-top:3px; margin-left:0; } @@ -180,13 +183,19 @@ margin-left:11px; float:left; width:90%; } - +.form_settings label.radio { +margin-top:0; +margin-right:47px; +margin-left:11px; +width:auto; +} #form_login p.form_guide, #form_register #settings_rememberme p.form_guide, #form_openid_login #settings_rememberme p.form_guide, #settings_twitter_remove p.form_guide, -#form_search ul.form_data #q { +#form_search ul.form_data #q, +#design_background-image_onoff p.form_guide { margin-left:0; } @@ -306,7 +315,6 @@ padding:4px 11px; border-width:1px; border-style:solid; border-bottom:0; -text-shadow: 2px 2px 2px #ddd; font-weight:bold; } #site_nav_local_views .nav { @@ -396,8 +404,8 @@ border-radius:7px; -moz-border-radius-topleft:0; -webkit-border-radius:7px; -webkit-border-top-left-radius:0; -border-style:solid; border-width:1px; +border-style:solid; } #shownotice #content { min-height:0; @@ -413,7 +421,7 @@ float:left; width:27.917%; min-height:259px; float:left; -margin-left:0.385%; +margin-left:0.5%; padding:1.795%; border-radius:7px; -moz-border-radius:7px; @@ -469,7 +477,6 @@ height:16px; #form_notice #notice_data-attach { left:183px; padding:0; - height:16px; } #form_notice .form_note { @@ -623,7 +630,8 @@ display:block; .entity_send-a-message a, .entity_edit a, .form_user_nudge input.submit, -.entity_nudge p { +.entity_nudge p, +.form_make_admin input.submit { border:0; padding-left:20px; } diff --git a/theme/base/css/ie.css b/theme/base/css/ie.css index d1b0558ec8..da200388eb 100644 --- a/theme/base/css/ie.css +++ b/theme/base/css/ie.css @@ -1,8 +1,6 @@ /* IE specific styles */ -legend { -margin-left:-7px; -} -input.checkbox { +input.checkbox, +input.radio { top:0; } #form_notice textarea { diff --git a/theme/base/images/icons/twotone/green/admin.gif b/theme/base/images/icons/twotone/green/admin.gif new file mode 100644 index 0000000000..10fa431cee Binary files /dev/null and b/theme/base/images/icons/twotone/green/admin.gif differ diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 166e62157b..881e264da8 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -9,7 +9,6 @@ @import url(../../base/css/display.css); -html, body, a:active { background-color:#C3D6DF; @@ -19,7 +18,7 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; font-size:1em; } address { -margin-right:7.18%; +margin-right:7.2%; } input, textarea, select, option { @@ -27,7 +26,7 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; } input, textarea, select, .entity_remote_subscribe { -border-color:#aaa; +border-color:#AAAAAA; } #filter_tags ul li { border-color:#C3D6DF; @@ -47,10 +46,13 @@ background-color:#A9BF4F; input:focus, textarea:focus, select:focus, #form_notice.warning #notice_data-text { border-color:#A9BF4F; +box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); +-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); +-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); } input.submit, .entity_remote_subscribe { -color:#fff; +color:#FFFFFF; } a, @@ -62,7 +64,8 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-primary { +.form_settings input.form_action-primary, +.form_make_admin input.submit { color:#002E6E; } @@ -79,10 +82,10 @@ background-color:#CEE1E9; } #notice_text-count { -color:#333; +color:#333333; } #form_notice.warning #notice_text-count { -color:#000; +color:#000000; } #form_notice label[for=notice_data-attach] { background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; @@ -92,27 +95,38 @@ opacity:0; } #form_notice.processing #notice_action-submit { -background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; +background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; cursor:wait; text-indent:-9999px; } +#content { +box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3); +-moz-box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3); +-webkit-box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3); +} #content, #site_nav_local_views a, #aside_primary { -border-color:#fff; +border-color:transparent; } #content, #site_nav_local_views .current a { -background-color:#fff; +background-color:#FFFFFF; } #site_nav_local_views a { -background-color:rgba(255, 255, 255, 0.2); +background-color:rgba(194, 194, 194, 0.5); +box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5); +-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5); +-webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5); } #site_nav_local_views a:hover { background-color:rgba(255, 255, 255, 0.7); } +#site_nav_local_views .current a { +text-shadow: rgba(194,194,194,0.5) 1px 1px 1px; +} .error { background-color:#F7E8E8; @@ -123,8 +137,8 @@ background-color:#EFF3DC; #anon_notice { background-color:#C3D6DF; -color:#fff; -border-color:#fff; +color:#FFFFFF; +border-color:#FFFFFF; } #showstream #anon_notice { @@ -152,7 +166,8 @@ background-image:url(../../base/images/icons/icon_foaf.gif); .form_user_unblock input.submit, .form_group_block input.submit, .form_group_unblock input.submit, -.entity_nudge p { +.entity_nudge p, +.form_make_admin input.submit { background-position: 0 40%; background-repeat: no-repeat; background-color:transparent; @@ -162,7 +177,7 @@ background-color:transparent; .form_user_subscribe input.submit, .form_user_unsubscribe input.submit { background-color:#A9BF4F; -color:#fff; +color:#FFFFFF; } .form_user_unsubscribe input.submit, .form_group_leave input.submit, @@ -186,6 +201,9 @@ background-image:url(../../base/images/icons/twotone/green/mail.gif); .form_group_unblock input.submit { background-image:url(../../base/images/icons/twotone/green/shield.gif); } +.form_make_admin input.submit { +background-image:url(../../base/images/icons/twotone/green/admin.gif); +} /* NOTICES */ .notice .attachment { @@ -212,24 +230,25 @@ background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-r } .notices div.entry-content, -.notices div.notice-options, -.notices li.hover .notices div.entry-content, -.notices li.hover .notices div.notice-options { +.notices div.notice-options { opacity:0.4; } -.notices li.hover div.entry-content, -.notices li.hover div.notice-options { +.notices li:hover div.entry-content, +.notices li:hover div.notice-options { opacity:1; } div.entry-content { -color:#333; +color:#333333; } div.notice-options a, div.notice-options input { font-family:sans-serif; } -.notices li.hover { -background-color:#fcfcfc; +.notices li:hover { +background-color:#FCFCFC; +} +#conversation .notices li:hover { +background-color:transparent; } .notices .notices { diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index cab42f16f4..ad57a0f0e4 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -9,7 +9,6 @@ @import url(../../base/css/display.css); -html, body, a:active { background-color:#F0F2F5; @@ -19,7 +18,7 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; font-size:1em; } address { -margin-right:7.18%; +margin-right:7.2%; } input, textarea, select, option { @@ -27,10 +26,10 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; } input, textarea, select, .entity_remote_subscribe { -border-color:#aaa; +border-color:#AAAAAA; } #filter_tags ul li { -border-color:#ddd; +border-color:#DDDDDD; } .form_settings input.form_action-primary { @@ -47,10 +46,13 @@ background-color:#9BB43E; input:focus, textarea:focus, select:focus, #form_notice.warning #notice_data-text { border-color:#9BB43E; +box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); +-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); +-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); } input.submit, .entity_remote_subscribe { -color:#fff; +color:#FFFFFF; } a, @@ -62,7 +64,8 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-primary { +.form_settings input.form_action-primary, +.form_make_admin input.submit { color:#002E6E; } @@ -79,10 +82,10 @@ background-color:#CEE1E9; } #notice_text-count { -color:#333; +color:#333333; } #form_notice.warning #notice_text-count { -color:#000; +color:#000000; } #form_notice label[for=notice_data-attach] { background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; @@ -92,27 +95,38 @@ opacity:0; } #form_notice.processing #notice_action-submit { -background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; +background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; cursor:wait; text-indent:-9999px; } +#content { +box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3); +-moz-box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3); +-webkit-box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3); +} #content, #site_nav_local_views a, #aside_primary { -border-color:#fff; +border-color:transparent; } #content, #site_nav_local_views .current a { -background-color:#fff; +background-color:#FFFFFF; } #site_nav_local_views a { -background-color:rgba(135, 180, 200, 0.3); +background-color:rgba(194, 194, 194, 0.5); +box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5); +-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5); +-webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5); } #site_nav_local_views a:hover { background-color:rgba(255, 255, 255, 0.7); } +#site_nav_local_views .current a { +text-shadow: rgba(194,194,194,0.5) 1px 1px 1px; +} .error { background-color:#F7E8E8; @@ -123,8 +137,8 @@ background-color:#EFF3DC; #anon_notice { background-color:#87B4C8; -color:#fff; -border-color:#fff; +color:#FFFFFF; +border-color:#FFFFFF; } #showstream #anon_notice { @@ -152,7 +166,8 @@ background-image:url(../../base/images/icons/icon_foaf.gif); .form_user_unblock input.submit, .form_group_block input.submit, .form_group_unblock input.submit, -.entity_nudge p { +.entity_nudge p, +.form_make_admin input.submit { background-position: 0 40%; background-repeat: no-repeat; background-color:transparent; @@ -162,7 +177,7 @@ background-color:transparent; .form_user_subscribe input.submit, .form_user_unsubscribe input.submit { background-color:#9BB43E; -color:#fff; +color:#FFFFFF; } .form_user_unsubscribe input.submit, .form_group_leave input.submit, @@ -186,6 +201,9 @@ background-image:url(../../base/images/icons/twotone/green/mail.gif); .form_group_unblock input.submit { background-image:url(../../base/images/icons/twotone/green/shield.gif); } +.form_make_admin input.submit { +background-image:url(../../base/images/icons/twotone/green/admin.gif); +} /* NOTICES */ .notice .attachment { @@ -212,24 +230,25 @@ background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-r } .notices div.entry-content, -.notices div.notice-options, -.notices li.hover .notices div.entry-content, -.notices li.hover .notices div.notice-options { +.notices div.notice-options { opacity:0.4; } -.notices li.hover div.entry-content, -.notices li.hover div.notice-options { +.notices li:hover div.entry-content, +.notices li:hover div.notice-options { opacity:1; } div.entry-content { -color:#333; +color:#333333; } div.notice-options a, div.notice-options input { font-family:sans-serif; } -.notices li.hover { -background-color:#fcfcfc; +.notices li:hover { +background-color:#FCFCFC; +} +#conversation .notices li:hover { +background-color:transparent; } .notices .notices {