1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

Phame V1 - Phabricator blogging software

Summary:
'cuz we need to be phamous!

V1 feature set

- posts
-- standard thing you'd expect - a title and a remarkup-powered body and...
-- "phame" title - a short string that can be used to reference the story. this gets auto-updated when you mess with the title.
-- configuration - for now, do you want Facebook, Disqus or no comments? this is a per-post thing but feeds from an instance-wide configuration

Please do toss out any must have features or changes.

Test Plan: played around with this bad boy like whoa

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, vrana

Maniphest Tasks: T1111

Differential Revision: https://secure.phabricator.com/D2202
This commit is contained in:
Bob Trahan 2012-04-12 13:09:04 -07:00
parent 3d6b8bff34
commit 51418900f7
35 changed files with 1832 additions and 0 deletions

View file

@ -489,6 +489,16 @@ return array(
// The Phabricator "Client Secret" to use for Phabricator API access.
'phabricator.application-secret' => null,
// -- Disqus Comments ------------------------------------------------------- //
// Should Phame users have Disqus comment widget, and if so what's the
// website shortname to use? For example, secure.phabricator.org uses
// "phabricator", which we registered with Disqus. If you aren't familiar
// with Disqus, see:
// Disqus quick start guide - http://docs.disqus.com/help/4/
// Information on shortnames - http://docs.disqus.com/help/68/
'disqus.shortname' => null,
// -- Recaptcha ------------------------------------------------------------- //
// Is Recaptcha enabled? If disabled, captchas will not appear. You should

View file

@ -0,0 +1,18 @@
CREATE DATABASE IF NOT EXISTS `phabricator_phame` COLLATE utf8_general_ci;
CREATE TABLE `phabricator_phame`.`phame_post` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`phid` VARCHAR(64) BINARY NOT NULL COLLATE utf8_bin,
`bloggerPHID` VARCHAR(64) BINARY NOT NULL COLLATE utf8_bin,
`title` VARCHAR(255) NOT NULL,
`phameTitle` VARCHAR(64) NOT NULL,
`body` LONGTEXT COLLATE utf8_general_ci,
`visibility` INT UNSIGNED NOT NULL DEFAULT 0,
`configData` LONGTEXT COLLATE utf8_general_ci,
`datePublished` INT UNSIGNED NOT NULL,
`dateCreated` INT UNSIGNED NOT NULL,
`dateModified` INT UNSIGNED NOT NULL,
KEY `bloggerPosts` (`bloggerPHID`, `visibility`, `datePublished`, `id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `phameTitle` (`bloggerPHID`, `phameTitle`)
) ENGINE=InnoDB;

View file

@ -1281,6 +1281,19 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/core/behavior-watch-anchor.js',
),
'javelin-behavior-phame-post-preview' =>
array(
'uri' => '/res/ac4c503a/rsrc/js/application/phame/phame-post-preview.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'phabricator-shaped-request',
),
'disk' => '/rsrc/js/application/phame/phame-post-preview.js',
),
'javelin-behavior-phriction-document-preview' =>
array(
'uri' => '/res/f1665ecd/rsrc/js/application/phriction/phriction-document-preview.js',

View file

@ -929,6 +929,19 @@ phutil_register_library_map(array(
'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol',
'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel',
'PhabricatorXHProfProfileView' => 'applications/xhprof/view/base',
'PhameController' => 'applications/phame/controller/base',
'PhameDAO' => 'applications/phame/storage/base',
'PhameDraftListController' => 'applications/phame/controller/post/list/drafts',
'PhamePost' => 'applications/phame/storage/post',
'PhamePostDeleteController' => 'applications/phame/controller/post/delete',
'PhamePostDetailView' => 'applications/phame/view/postdetail',
'PhamePostEditController' => 'applications/phame/controller/post/edit',
'PhamePostListBaseController' => 'applications/phame/controller/post/list/base',
'PhamePostListController' => 'applications/phame/controller/post/list/posts',
'PhamePostListView' => 'applications/phame/view/postlist',
'PhamePostPreviewController' => 'applications/phame/controller/post/preview',
'PhamePostQuery' => 'applications/phame/query/post',
'PhamePostViewController' => 'applications/phame/controller/post/view',
'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/monthyearexpiry',
'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/base',
'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/paymentform',
@ -1743,6 +1756,19 @@ phutil_register_library_map(array(
'PhabricatorXHProfProfileSymbolView' => 'PhabricatorXHProfProfileView',
'PhabricatorXHProfProfileTopLevelView' => 'PhabricatorXHProfProfileView',
'PhabricatorXHProfProfileView' => 'AphrontView',
'PhameController' => 'PhabricatorController',
'PhameDAO' => 'PhabricatorLiskDAO',
'PhameDraftListController' => 'PhamePostListBaseController',
'PhamePost' => 'PhameDAO',
'PhamePostDeleteController' => 'PhameController',
'PhamePostDetailView' => 'AphrontView',
'PhamePostEditController' => 'PhameController',
'PhamePostListBaseController' => 'PhameController',
'PhamePostListController' => 'PhamePostListBaseController',
'PhamePostListView' => 'AphrontView',
'PhamePostPreviewController' => 'PhameController',
'PhamePostQuery' => 'PhabricatorOffsetPagedQuery',
'PhamePostViewController' => 'PhameController',
'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
'PhortuneStripeBaseController' => 'PhabricatorController',
'PhortuneStripePaymentFormView' => 'AphrontView',

View file

@ -385,6 +385,28 @@ class AphrontDefaultApplicationConfiguration
'diff/(?P<id>\d+)/' => 'PhrictionDiffController',
),
'/phame/' => array(
'' => 'PhamePostListController',
'post/' => array(
'' => 'PhamePostListController',
'delete/(?P<phid>[^/]+)/' => 'PhamePostDeleteController',
'edit/(?P<phid>[^/]+)/' => 'PhamePostEditController',
'new/' => 'PhamePostEditController',
'preview/' => 'PhamePostPreviewController',
'view/(?P<phid>[^/]+)/' => 'PhamePostViewController',
),
'draft/' => array(
'' => 'PhameDraftListController',
'new/' => 'PhamePostEditController',
),
'posts/' => array(
'' => 'PhamePostListController',
'(?P<bloggername>\w+)/' => 'PhamePostListController',
'(?P<bloggername>\w+)/(?P<phametitle>.+/)'
=> 'PhamePostViewController',
),
),
'/calendar/' => array(
'' => 'PhabricatorCalendarBrowseController',
),

View file

@ -49,6 +49,13 @@ class PhabricatorMarkupEngine {
));
}
public static function newPhameMarkupEngine() {
return self::newMarkupEngine(array(
'macros' => false,
));
}
public static function newDifferentialMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
'custom-inline' => PhabricatorEnv::getEnvConfig(

View file

@ -0,0 +1,91 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
abstract class PhameController extends PhabricatorController {
private $showSideNav;
protected function setShowSideNav($value) {
$this->showSideNav = (bool) $value;
return $this;
}
private function showSideNav() {
return $this->showSideNav;
}
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->setApplicationName('Phame');
$page->setBaseURI('/phame/');
$page->setTitle(idx($data, 'title'));
$page->setGlyph("\xe2\x9c\xa9");
$tabs = array(
'help' => array(
'name' => 'Help',
'href' =>
PhabricatorEnv::getDoclink('article/Phame_User_Guide.html'),
),
);
$page->setTabs($tabs, idx($data, 'tab'));
if ($this->showSideNav()) {
$nav = $this->renderSideNavFilterView($this->getSideNavFilter());
$nav->appendChild($view);
$page->appendChild($nav);
} else {
$page->appendChild($view);
}
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
}
private function renderSideNavFilterView($filter) {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/phame/'));
$nav->addLabel('Drafts');
$nav->addFilter('post/new',
'New Draft');
$nav->addFilter('draft',
'My Drafts');
$nav->addSpacer();
$nav->addLabel('Posts');
$nav->addFilter('post',
'My Posts');
foreach ($this->getSideNavExtraPostFilters() as $post_filter) {
$nav->addFilter($post_filter['key'],
$post_filter['name']);
}
$nav->selectFilter($filter, 'post');
return $nav;
}
protected function getSideNavExtraPostFilters() {
return array();
}
protected function getSideNavFilter() {
return 'post';
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/webpage');
phutil_require_module('phabricator', 'applications/base/controller/base');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'view/layout/sidenavfilter');
phutil_require_module('phutil', 'parser/uri');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhameController.php');

View file

@ -0,0 +1,81 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhamePostDeleteController
extends PhameController {
private $phid;
private function setPostPHID($phid) {
$this->phid = $phid;
return $this;
}
private function getPostPHID() {
return $this->phid;
}
protected function getSideNavFilter() {
return 'post/delete/'.$this->getPostPHID();
}
protected function getSideNavExtraPostFilters() {
$filters = array(
array('key' => $this->getSideNavFilter(),
'name' => 'Delete Post')
);
return $filters;
}
public function willProcessRequest(array $data) {
$phid = $data['phid'];
$this->setPostPHID($phid);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$post = id(new PhamePost())->loadOneWhere(
'phid = %s',
$this->getPostPHID());
if (empty($post)) {
return new Aphront404Response();
}
if ($post->getBloggerPHID() != $user->getPHID()) {
return new Aphront403Response();
}
$edit_uri = $post->getEditURI();
if ($request->isFormPost()) {
$post->delete();
return id(new AphrontRedirectResponse())->setURI('/phame/?deleted');
}
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle('Delete post?')
->appendChild('Really delete this post? It will be gone forever.')
->addSubmitButton('Delete')
->addCancelButton($edit_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -0,0 +1,20 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/403');
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/dialog');
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/phame/controller/base');
phutil_require_module('phabricator', 'applications/phame/storage/post');
phutil_require_module('phabricator', 'view/dialog');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhamePostDeleteController.php');

View file

@ -0,0 +1,269 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhamePostEditController
extends PhameController {
private $phid;
private $isPostEdit;
private function setPostPHID($phid) {
$this->phid = $phid;
return $this;
}
private function getPostPHID() {
return $this->phid;
}
private function setIsPostEdit($is_post_edit) {
$this->isPostEdit = $is_post_edit;
return $this;
}
private function isPostEdit() {
return $this->isPostEdit;
}
protected function getSideNavFilter() {
if ($this->isPostEdit()) {
$filter = 'post/edit/'.$this->getPostPHID();
} else {
$filter = 'post/new';
}
return $filter;
}
protected function getSideNavExtraPostFilters() {
if ($this->isPostEdit()) {
$filters = array(
array('key' => 'post/edit/'.$this->getPostPHID(),
'name' => 'Edit Post')
);
} else {
$filters = array();
}
return $filters;
}
public function willProcessRequest(array $data) {
$phid = idx($data, 'phid');
$this->setPostPHID($phid);
$this->setIsPostEdit((bool) $phid);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$e_phame_title = null;
$e_title = null;
$errors = array();
if ($this->isPostEdit()) {
$post = id(new PhamePost())->loadOneWhere(
'phid = %s',
$this->getPostPHID());
if (empty($post)) {
return new Aphront404Response();
}
if ($post->getBloggerPHID() != $user->getPHID()) {
return new Aphront403Response();
}
$cancel_uri = $post->getViewURI($user->getUsername());
$submit_button = 'Save Changes';
$delete_button = javelin_render_tag(
'a',
array(
'href' => $post->getDeleteURI(),
'class' => 'grey button',
'sigil' => 'workflow',
),
'Delete Post');
$page_title = 'Edit Post';
} else {
$post = id(new PhamePost())
->setBloggerPHID($user->getPHID())
->setVisibility(PhamePost::VISIBILITY_DRAFT);
$cancel_uri = '/phame';
$submit_button = 'Create Post';
$delete_button = null;
$page_title = 'Create Post';
}
if ($request->isFormPost()) {
$saved = true;
$visibility = $request->getInt('visibility');
$comments = $request->getStr('comments_widget');
$data = array('comments_widget' => $comments);
$phame_title = $request->getStr('phame_title');
$phame_title = PhabricatorSlug::normalize($phame_title);
$title = $request->getStr('title');
$post->setTitle($title);
$post->setPhameTitle($phame_title);
$post->setBody($request->getStr('body'));
$post->setVisibility($visibility);
$post->setConfigData($data);
// only publish once...!
if ($visibility == PhamePost::VISIBILITY_PUBLISHED) {
if (!$post->getDatePublished()) {
$post->setDatePublished(time());
}
// this is basically a cast of null to 0 if its a new post
} else if (!$post->getDatePublished()) {
$post->setDatePublished(0);
}
if ($phame_title == '/') {
$errors[] = 'Phame title must be nonempty.';
$e_phame_title = 'Required';
}
if (empty($title)) {
$errors[] = 'Title must be nonempty.';
$e_title = 'Required';
}
if (empty($errors)) {
try {
$post->save();
} catch (AphrontQueryDuplicateKeyException $e) {
$saved = false;
$e_phame_title = 'Not Unique';
$errors[] = 'Another post already uses this slug. '.
'Each post must have a unique slug.';
}
} else {
$saved = false;
}
if ($saved) {
$uri = new PhutilURI($post->getViewURI($user->getUsername()));
return id(new AphrontRedirectResponse())
->setURI($uri);
}
}
$panel = new AphrontPanelView();
$panel->setHeader($page_title);
$panel->setWidth(AphrontPanelView::WIDTH_FULL);
if ($delete_button) {
$panel->addButton($delete_button);
}
$remarkup_reference = phutil_render_tag(
'a',
array(
'href' =>
PhabricatorEnv::getDoclink('article/Remarkup_Reference.html'),
'tabindex' => '-1',
'target' => '_blank',
),
'Formatting Reference');
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Title')
->setName('title')
->setValue($post->getTitle())
->setID('post-title')
->setError($e_title)
)
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Phame Title')
->setName('phame_title')
->setValue(rtrim($post->getPhameTitle(), '/'))
->setID('post-phame-title')
->setCaption('Up to 64 alphanumeric characters '.
'with underscores for spaces. '.
'Formatting is enforced.')
->setError($e_phame_title)
)
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Body')
->setName('body')
->setValue($post->getBody())
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setEnableDragAndDropFileUploads(true)
->setID('post-body')
->setCaption($remarkup_reference)
)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Visibility')
->setName('visibility')
->setValue($post->getVisibility())
->setOptions(PhamePost::getVisibilityOptionsForSelect())
)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Comments Widget')
->setName('comments_widget')
->setvalue($post->getCommentsWidget())
->setOptions($post->getCommentsWidgetOptionsForSelect())
)
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($submit_button)
);
$panel->appendChild($form);
$preview_panel =
'<div class="aphront-panel-preview ">
<div class="phame-post-preview-header">
Post Preview
</div>
<div id="post-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
Javelin::initBehavior(
'phame-post-preview',
array(
'preview' => 'post-preview',
'body' => 'post-body',
'title' => 'post-title',
'phame_title' => 'post-phame-title',
'uri' => '/phame/post/preview/',
));
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle('Errors saving post.')
->setErrors($errors);
} else {
$error_view = null;
}
$this->setShowSideNav(true);
return $this->buildStandardPageResponse(
array(
$error_view,
$panel,
$preview_panel,
),
array(
'title' => $page_title,
));
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/403');
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/phame/controller/base');
phutil_require_module('phabricator', 'applications/phame/storage/post');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'infrastructure/javelin/api');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/select');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/control/textarea');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'parser/uri');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhamePostEditController.php');

View file

@ -0,0 +1,127 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
abstract class PhamePostListBaseController
extends PhameController {
private $bloggerName;
private $isDraft;
private function setBloggerName($blogger_name) {
$this->bloggerName = $blogger_name;
return $this;
}
private function getBloggerName() {
return $this->bloggerName;
}
protected function getSideNavExtraPostFilters() {
if ($this->isDraft() || !$this->getBloggerName()) {
return array();
}
return
array(array('key' => $this->getSideNavFilter(),
'name' => 'Posts by '.$this->getBloggerName()));
}
protected function getSideNavFilter() {
if ($this->getBloggerName()) {
$filter = 'posts/'.$this->getBloggerName();
} else if ($this->isDraft()) {
$filter = 'draft';
} else {
$filter = 'posts';
}
return $filter;
}
private function isDraft() {
return (bool) $this->isDraft;
}
protected function setIsDraft($is_draft) {
$this->isDraft = $is_draft;
return $this;
}
public function willProcessRequest(array $data) {
$this->setBloggerName(idx($data, 'bloggername'));
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$pager = new AphrontPagerView();
$page_size = 50;
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setPageSize($page_size);
$pager->setOffset($request->getInt('offset'));
if ($this->getBloggerName()) {
$blogger = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$this->getBloggerName());
if (!$blogger) {
return new Aphront404Response();
}
$page_title = 'Posts by '.$this->getBloggerName();
if ($blogger->getPHID() == $user->getPHID()) {
$actions = array('view', 'edit');
} else {
$actions = array('view');
}
$this->setShowSideNav(false);
} else {
$blogger = $user;
$page_title = 'Posts by '.$user->getUserName();
$actions = array('view', 'edit');
$this->setShowSideNav(true);
}
$phid = $blogger->getPHID();
// user gets to see their own unpublished stuff
if ($phid == $user->getPHID() && $this->isDraft()) {
$post_visibility = PhamePost::VISIBILITY_DRAFT;
} else {
$post_visibility = PhamePost::VISIBILITY_PUBLISHED;
}
$query = new PhamePostQuery();
$query->withBloggerPHID($phid);
$query->withVisibility($post_visibility);
$posts = $query->executeWithPager($pager);
$bloggers = array($blogger->getPHID() => $blogger);
$panel = id(new PhamePostListView())
->setUser($user)
->setBloggers($bloggers)
->setPosts($posts)
->setActions($actions)
->setDraftList($this->isDraft());
return $this->buildStandardPageResponse(
array(
$panel,
$pager
),
array(
'title' => $page_title,
));
}
}

View file

@ -0,0 +1,20 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'applications/phame/controller/base');
phutil_require_module('phabricator', 'applications/phame/query/post');
phutil_require_module('phabricator', 'applications/phame/storage/post');
phutil_require_module('phabricator', 'applications/phame/view/postlist');
phutil_require_module('phabricator', 'view/control/pager');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhamePostListBaseController.php');

View file

@ -0,0 +1,29 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhameDraftListController
extends PhamePostListBaseController {
public function processRequest() {
$this->setIsDraft(true);
return parent::processRequest();
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/phame/controller/post/list/base');
phutil_require_source('PhameDraftListController.php');

View file

@ -0,0 +1,29 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhamePostListController
extends PhamePostListBaseController {
public function processRequest() {
$this->setIsDraft(false);
return parent::processRequest();
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/phame/controller/post/list/base');
phutil_require_source('PhamePostListController.php');

View file

@ -0,0 +1,51 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhamePostPreviewController
extends PhameController {
protected function getSideNavFilter() {
return null;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$body = $request->getStr('body');
$title = $request->getStr('title');
$phame_title = $request->getStr('phame_title');
$post = id(new PhamePost())
->setBody($body)
->setTitle($title)
->setPhameTitle($phame_title)
->setDateModified(time());
$post_html = id(new PhamePostDetailView())
->setUser($user)
->setBlogger($user)
->setPost($post)
->setIsPreview(true)
->render();
return id(new AphrontAjaxResponse())->setContent($post_html);
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/ajax');
phutil_require_module('phabricator', 'applications/phame/controller/base');
phutil_require_module('phabricator', 'applications/phame/storage/post');
phutil_require_module('phabricator', 'applications/phame/view/postdetail');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhamePostPreviewController.php');

View file

@ -0,0 +1,151 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhamePostViewController
extends PhameController {
private $postPHID;
private $phameTitle;
private $bloggerName;
private function setPostPHID($post_phid) {
$this->postPHID = $post_phid;
return $this;
}
private function getPostPHID() {
return $this->postPHID;
}
private function setPhameTitle($phame_title) {
$this->phameTitle = $phame_title;
return $this;
}
private function getPhameTitle() {
return $this->phameTitle;
}
private function setBloggerName($blogger_name) {
$this->bloggerName = $blogger_name;
return $this;
}
private function getBloggerName() {
return $this->bloggerName;
}
protected function getSideNavFilter() {
$filter = 'post/view/'.$this->getPostPHID();
return $filter;
}
protected function getSideNavExtraPostFilters() {
$filters = array(
array('key' => $this->getSideNavFilter(),
'name' => $this->getPhameTitle())
);
return $filters;
}
public function willProcessRequest(array $data) {
$this->setPostPHID(idx($data, 'phid'));
$this->setPhameTitle(idx($data, 'phametitle'));
$this->setBloggerName(idx($data, 'bloggername'));
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$post_phid = null;
if ($this->getPostPHID()) {
$post_phid = $this->getPostPHID();
if (!$post_phid) {
return new Aphront404Response();
}
$post = id(new PhamePost())->loadOneWhere(
'phid = %s',
$post_phid);
if ($post) {
$this->setPhameTitle($post->getPhameTitle());
}
$blogger = id(new PhabricatorUser())->loadOneWhere(
'phid = %s', $post->getBloggerPHID());
if (!$blogger) {
return new Aphront404Response();
}
} else if ($this->getBloggerName() && $this->getPhameTitle()) {
$phame_title = $this->getPhameTitle();
$phame_title = PhabricatorSlug::normalize($phame_title);
if ($phame_title != $this->getPhameTitle()) {
$uri = $post->getViewURI($this->getBloggerName());
return id(new AphrontRedirectResponse())->setURI($uri);
}
$blogger = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$this->getBloggerName());
if (!$blogger) {
return new Aphront404Response();
}
$post = id(new PhamePost())->loadOneWhere(
'bloggerPHID = %s AND phameTitle = %s',
$blogger->getPHID(),
$this->getPhameTitle());
}
if (!$post) {
return new Aphront404Response();
}
if ($post->isDraft() &&
$post->getBloggerPHID() != $user->getPHID()) {
return new Aphront404Response();
}
if ($post->isDraft()) {
$notice = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle('You are previewing a draft.')
->setErrors(array(
'Only you can see this draft until you publish it.',
'If you chose a comment widget it will show up when you publish.',
));
} else {
$notice = null;
}
$page_title = $this->getPhameTitle();
$page = id(new PhamePostDetailView())
->setUser($user)
->setRequestURI($request->getRequestURI())
->setBlogger($blogger)
->setPost($post);
$this->setShowSideNav(false);
return $this->buildStandardPageResponse(
array(
$notice,
$page,
),
array(
'title' => $page_title,
));
}
}

View file

@ -0,0 +1,21 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/people/storage/user');
phutil_require_module('phabricator', 'applications/phame/controller/base');
phutil_require_module('phabricator', 'applications/phame/storage/post');
phutil_require_module('phabricator', 'applications/phame/view/postdetail');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhamePostViewController.php');

View file

@ -0,0 +1,79 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhamePostQuery extends PhabricatorOffsetPagedQuery {
private $bloggerPHID;
private $visibility;
public function withBloggerPHID($blogger_phid) {
$this->bloggerPHID = $blogger_phid;
return $this;
}
public function withVisibility($visibility) {
$this->visibility = $visibility;
return $this;
}
public function execute() {
$table = new PhamePost();
$conn_r = $table->establishConnection('r');
$where_clause = $this->buildWhereClause($conn_r);
$order_clause = $this->buildOrderClause($conn_r);
$limit_clause = $this->buildLimitClause($conn_r);
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T e %Q %Q %Q',
$table->getTableName(),
$where_clause,
$order_clause,
$limit_clause);
$posts = $table->loadAllFromArray($data);
return $posts;
}
private function buildWhereClause($conn_r) {
$where = array();
if ($this->bloggerPHID) {
$where[] = qsprintf(
$conn_r,
'bloggerPHID = %s',
$this->bloggerPHID
);
}
if ($this->visibility !== null) {
$where[] = qsprintf(
$conn_r,
'visibility = %d',
$this->visibility
);
}
return $this->formatWhereClause($where);
}
private function buildOrderClause($conn_r) {
return 'ORDER BY datePublished DESC, id DESC';
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/phame/storage/post');
phutil_require_module('phabricator', 'infrastructure/query/offsetpaged');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_source('PhamePostQuery.php');

View file

@ -0,0 +1,28 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
abstract class PhameDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'phame';
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/base/storage/lisk');
phutil_require_source('PhameDAO.php');

View file

@ -0,0 +1,106 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhamePost extends PhameDAO {
const VISIBILITY_DRAFT = 0;
const VISIBILITY_PUBLISHED = 1;
protected $id;
protected $phid;
protected $bloggerPHID;
protected $title;
protected $phameTitle;
protected $body;
protected $visibility;
protected $configData;
protected $datePublished;
public function getViewURI($blogger_name = '') {
// go for the pretty uri if we can
if ($blogger_name) {
$phame_title = PhabricatorSlug::normalize($this->getPhameTitle());
$uri = phutil_escape_uri('/phame/posts/'.$blogger_name.'/'.$phame_title);
} else {
$uri = $this->getActionURI('view');
}
return $uri;
}
public function getEditURI() {
return $this->getActionURI('edit');
}
public function getDeleteURI() {
return $this->getActionURI('delete');
}
private function getActionURI($action) {
return '/phame/post/'.$action.'/'.$this->getPHID().'/';
}
public function isDraft() {
return $this->getVisibility() == self::VISIBILITY_DRAFT;
}
public function getCommentsWidget() {
$config_data = $this->getConfigData();
if (empty($config_data)) {
return 'none';
}
return idx($config_data, 'comments_widget', 'none');
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'configData' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_POST);
}
public static function getVisibilityOptionsForSelect() {
return array(
self::VISIBILITY_DRAFT => 'Draft: visible only to me.',
self::VISIBILITY_PUBLISHED => 'Published: visible to the whole world.',
);
}
public function getCommentsWidgetOptionsForSelect() {
$current = $this->getCommentsWidget();
$options = array();
if ($current == 'facebook' ||
PhabricatorEnv::getEnvConfig('facebook.application-id')) {
$options['facebook'] = 'Facebook';
}
if ($current == 'disqus' ||
PhabricatorEnv::getEnvConfig('disqus.shortname')) {
$options['disqus'] = 'Disqus';
}
$options['none'] = 'None';
return $options;
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/phame/storage/base');
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/storage/phid');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'infrastructure/util/slug');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhamePost.php');

View file

@ -0,0 +1,221 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhamePostDetailView extends AphrontView {
private $post;
private $blogger;
private $requestURI;
private $user;
private $isPreview;
public function setIsPreview($is_preview) {
$this->isPreview = $is_preview;
return $this;
}
private function isPreview() {
return $this->isPreview;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setRequestURI(PhutilURI $uri) {
$uri = PhabricatorEnv::getProductionURI($uri->setQueryParams(array()));
$this->requestURI = $uri;
return $this;
}
private function getRequestURI() {
return $this->requestURI;
}
public function setBlogger(PhabricatorUser $blogger) {
$this->blogger = $blogger;
return $this;
}
private function getBlogger() {
return $this->blogger;
}
public function setPost(PhamePost $post) {
$this->post = $post;
return $this;
}
private function getPost() {
return $this->post;
}
public function render() {
require_celerity_resource('phabricator-remarkup-css');
$user = $this->getUser();
$blogger = $this->getBlogger();
$post = $this->getPost();
$engine = PhabricatorMarkupEngine::newPhameMarkupEngine();
$body = $engine->markupText($post->getBody());
if ($post->isDraft()) {
$uri = '/phame/draft/';
$label = 'Back to Your Drafts';
} else {
$uri = '/phame/posts/'.$blogger->getUsername();
$label = 'More Posts by '.phutil_escape_html($blogger->getUsername());
}
$button = phutil_render_tag(
'a',
array(
'href' => $uri,
'class' => 'grey button',
),
$label
);
$publish_date = $post->getDatePublished();
if ($publish_date) {
$caption = 'Published '.
phabricator_datetime($publish_date,
$user);
} else {
$caption = 'Last edited '.
phabricator_datetime($post->getDateModified(),
$user);
}
if ($this->isPreview()) {
$width = AphrontPanelView::WIDTH_FULL;
} else {
$width = AphrontPanelView::WIDTH_WIDE;
}
$panel = id(new AphrontPanelView())
->setHeader(phutil_escape_html($post->getTitle()))
->appendChild('<div class="phabricator-remarkup">'.$body.'</div>')
->setWidth($width)
->addButton($button)
->setCaption($caption);
if ($user->getPHID() == $post->getBloggerPHID()) {
if ($post->isDraft()) {
$label = 'Edit Draft';
} else {
$label = 'Edit Post';
}
$button = phutil_render_tag(
'a',
array(
'href' => $post->getEditURI(),
'class' => 'grey button',
),
$label);
$panel->addButton($button);
}
switch ($post->getCommentsWidget()) {
case 'facebook':
$comments = $this->renderFacebookComments();
break;
case 'disqus':
$comments = $this->renderDisqusComments();
break;
case 'none':
default:
$comments = null;
break;
}
$panel->appendChild($comments);
return $panel->render();
}
private function renderFacebookComments() {
$fb_id = PhabricatorEnv::getEnvConfig('facebook.application-id');
if (!$fb_id) {
return null;
}
$fb_root = phutil_render_tag('div',
array(
'id' => 'fb-root',
)
);
$c_uri = '//connect.facebook.net/en_US/all.js#xfbml=1&appId='.$fb_id;
$fb_js = jsprintf(
'<script>(function(d, s, id) {'.
' var js, fjs = d.getElementsByTagName(s)[0];'.
' if (d.getElementById(id)) return;'.
' js = d.createElement(s); js.id = id;'.
' js.src = %s;'.
' fjs.parentNode.insertBefore(js, fjs);'.
'}(document, \'script\', \'facebook-jssdk\'));</script>',
$c_uri
);
$fb_comments = phutil_render_tag('div',
array(
'class' => 'fb-comments',
'data-href' => $this->getRequestURI(),
'data-num-posts' => 5,
'data-width' => 1080,
'data-colorscheme' => 'dark',
)
);
return '<hr />' . $fb_root . $fb_js . $fb_comments;
}
private function renderDisqusComments() {
$disqus_shortname = PhabricatorEnv::getEnvConfig('disqus.shortname');
if (!$disqus_shortname) {
return null;
}
$post = $this->getPost();
$disqus_thread = phutil_render_tag('div',
array(
'id' => 'disqus_thread'
)
);
// protip - try some var disqus_developer = 1; action to test locally
$disqus_js = jsprintf(
'<script>'.
' var disqus_shortname = "phabricator";'.
' var disqus_identifier = %s;'.
' var disqus_url = %s;'.
' var disqus_title = %s;'.
'(function() {'.
' var dsq = document.createElement("script");'.
' dsq.type = "text/javascript";'.
' dsq.async = true;'.
' dsq.src = "http://" + disqus_shortname + ".disqus.com/embed.js";'.
'(document.getElementsByTagName("head")[0] ||'.
' document.getElementsByTagName("body")[0]).appendChild(dsq);'.
'})(); </script>',
$post->getPHID(),
$this->getRequestURI(),
$post->getTitle()
);
return '<hr />' . $disqus_thread . $disqus_js;
}
}

View file

@ -0,0 +1,21 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/markup/engine');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'view/base');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_module('phutil', 'xsprintf/jsprintf');
phutil_require_source('PhamePostDetailView.php');

View file

@ -0,0 +1,137 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phame
*/
final class PhamePostListView extends AphrontView {
private $user;
private $posts;
private $bloggers;
private $actions;
private $draftList;
public function setDraftList($draft_list) {
$this->draftList = $draft_list;
return $this;
}
public function isDraftList() {
return (bool) $this->draftList;
}
private function getPostNoun() {
if ($this->isDraftList()) {
$noun = 'Draft';
} else {
$noun = 'Post';
}
return $noun;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
private function getUser() {
return $this->user;
}
public function setPosts(array $posts) {
assert_instances_of($posts, 'PhamePost');
$this->posts = $posts;
return $this;
}
private function getPosts() {
return $this->posts;
}
public function setBloggers(array $bloggers) {
assert_instances_of($bloggers, 'PhabricatorUser');
$this->bloggers = $bloggers;
return $this;
}
private function getBloggers() {
return $this->bloggers;
}
public function setActions(array $actions) {
$this->actions = $actions;
return $this;
}
private function getActions() {
if ($this->actions) {
return $this->actions;
}
return array();
}
public function render() {
$user = $this->getUser();
$posts = $this->getPosts();
$bloggers = $this->getBloggers();
$noun = $this->getPostNoun();
if (empty($posts)) {
$panel = id(new AphrontPanelView())
->setHeader(sprintf('No %ss... Yet!', $noun))
->setCaption('Will you answer the call to phame?')
->setCreateButton(sprintf('New %s', $noun),
sprintf('/phame/%s/new', strtolower($noun)));
return $panel->render();
}
require_celerity_resource('phabricator-remarkup-css');
$engine = PhabricatorMarkupEngine::newPhameMarkupEngine();
$html = array();
$actions = $this->getActions();
foreach ($posts as $post) {
$blogger_phid = $post->getBloggerPHID();
$blogger = $bloggers[$blogger_phid];
$updated = phabricator_datetime($post->getDateModified(),
$user);
$body = $engine->markupText($post->getBody());
$panel = id(new AphrontPanelView())
->setHeader(phutil_escape_html($post->getTitle()))
->setCaption('Last updated '.$updated)
->appendChild('<div class="phabricator-remarkup">'.$body.'</div>');
foreach ($actions as $action) {
switch ($action) {
case 'view':
$uri = $post->getViewURI($blogger->getUsername());
$label = 'View '.$noun;
break;
case 'edit':
$uri = $post->getEditURI();
$label = 'Edit '.$noun;
break;
default:
break;
}
$button = phutil_render_tag(
'a',
array(
'href' => $uri,
'class' => 'grey button',
),
$label);
$panel->addButton($button);
}
$html[] = $panel->render();
}
return implode('', $html);
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/markup/engine');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'view/base');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhamePostListView.php');

View file

@ -39,4 +39,5 @@ final class PhabricatorPHIDConstants {
const PHID_TYPE_DRYL = 'DRYL';
const PHID_TYPE_OASC = 'OASC';
const PHID_TYPE_OASA = 'OASA';
const PHID_TYPE_POST = 'POST';
}

View file

@ -0,0 +1,18 @@
@title Phame User Guide
@group userguide
Journal about your thoughts and feelings. Share with others. Profit.
= Overview =
Phame is a simple blogging platform. You can write drafts which only you can
see. Later, you can publish these drafts as posts which anyone can see.
= Comment Widgets =
Phame supports comment widgets from Facebook and Disqus. The adminstrator
of the Phabricator instance must properly configure Phabricator to enable
this functionality.
NOTE: Phame is extremely new and very basic for now. Give us feedback on
what you'd like to see improve!

View file

@ -0,0 +1,81 @@
/**
* @provides javelin-behavior-phame-post-preview
* @requires javelin-behavior
* javelin-dom
* javelin-util
* phabricator-shaped-request
*/
JX.behavior('phame-post-preview', function(config) {
var preview = JX.$(config.preview);
var body = JX.$(config.body);
var title = JX.$(config.title);
var phame_title = JX.$(config.phame_title);
var sync_titles = true;
var titleCallback = function(e) {
if (!sync_titles) {
return;
}
var title_string = new String(title.value);
phame_title.value = normalizeSlug(title_string);
}
var phameTitleKeyupCallback = function (e) {
// stop sync'ing once user edits phame_title directly
sync_titles = false;
var normalized = normalizeSlug(phame_title.value, true);
if (normalized == phame_title.value) {
return;
}
var position = phame_title.value.length;
if ('selectionStart' in phame_title) {
position = phame_title.selectionStart;
}
phame_title.value = normalized;
if ('setSelectionRange' in phame_title) {
phame_title.focus();
phame_title.setSelectionRange(position, position);
}
}
var phameTitleBlurCallback = function (e) {
phame_title.value = normalizeSlug(phame_title.value);
}
// This is a sort of implementation of PhabricatorSlug::normalize
var normalizeSlug = function (slug, spare_trailing_underscore) {
var s = slug.toLowerCase().replace(/[^a-z0-9/]+/g, '_').substr(0, 63);
if (spare_trailing_underscore) {
// do nothing
} else {
s = s.replace(/_$/g, '');
}
return s;
}
var callback = function(r) {
JX.DOM.setContent(JX.$(config.preview), JX.$H(r));
};
var getdata = function() {
return {
body : body.value,
title : title.value,
phame_title : phame_title.value
};
}
var request = new JX.PhabricatorShapedRequest(config.uri, callback, getdata);
var trigger = JX.bind(request, request.trigger);
JX.DOM.listen(body, 'keydown', null, trigger);
JX.DOM.listen(title, 'keydown', null, trigger);
JX.DOM.listen(title, 'keyup', null, titleCallback);
JX.DOM.listen(phame_title, 'keydown', null, trigger);
JX.DOM.listen(phame_title, 'keyup', null, phameTitleKeyupCallback);
JX.DOM.listen(phame_title, 'blur', null, phameTitleBlurCallback);
request.start();
});