mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Add hierarchical breadcrumbs to Phriction
Summary: Show ancestor pages when viewing a page in Phriction. Test Plan: https://secure.phabricator.com/file/view/PHID-FILE-042368dbadaa8ab826ec/ Reviewed By: hsb Reviewers: hsb, codeblock, jungejason, tuomaspelkonen, aran Commenters: aran CC: aran, hsb, epriestley Differential Revision: 654
This commit is contained in:
parent
4c44c44964
commit
73b0468f72
9 changed files with 205 additions and 16 deletions
|
@ -586,6 +586,7 @@ phutil_register_library_map(array(
|
||||||
'PhrictionDAO' => 'applications/phriction/storage/base',
|
'PhrictionDAO' => 'applications/phriction/storage/base',
|
||||||
'PhrictionDocument' => 'applications/phriction/storage/document',
|
'PhrictionDocument' => 'applications/phriction/storage/document',
|
||||||
'PhrictionDocumentController' => 'applications/phriction/controller/document',
|
'PhrictionDocumentController' => 'applications/phriction/controller/document',
|
||||||
|
'PhrictionDocumentTestCase' => 'applications/phriction/storage/document/__tests__',
|
||||||
'PhrictionEditController' => 'applications/phriction/controller/edit',
|
'PhrictionEditController' => 'applications/phriction/controller/edit',
|
||||||
'PhrictionHistoryController' => 'applications/phriction/controller/history',
|
'PhrictionHistoryController' => 'applications/phriction/controller/history',
|
||||||
'PhrictionListController' => 'applications/phriction/controller/list',
|
'PhrictionListController' => 'applications/phriction/controller/list',
|
||||||
|
@ -1085,6 +1086,7 @@ phutil_register_library_map(array(
|
||||||
'PhrictionDAO' => 'PhabricatorLiskDAO',
|
'PhrictionDAO' => 'PhabricatorLiskDAO',
|
||||||
'PhrictionDocument' => 'PhrictionDAO',
|
'PhrictionDocument' => 'PhrictionDAO',
|
||||||
'PhrictionDocumentController' => 'PhrictionController',
|
'PhrictionDocumentController' => 'PhrictionController',
|
||||||
|
'PhrictionDocumentTestCase' => 'PhabricatorTestCase',
|
||||||
'PhrictionEditController' => 'PhrictionController',
|
'PhrictionEditController' => 'PhrictionController',
|
||||||
'PhrictionHistoryController' => 'PhrictionController',
|
'PhrictionHistoryController' => 'PhrictionController',
|
||||||
'PhrictionListController' => 'PhrictionController',
|
'PhrictionListController' => 'PhrictionController',
|
||||||
|
|
|
@ -42,6 +42,8 @@ class PhrictionDocumentController
|
||||||
'slug = %s',
|
'slug = %s',
|
||||||
$slug);
|
$slug);
|
||||||
|
|
||||||
|
$breadcrumbs = $this->renderBreadcrumbs($slug);
|
||||||
|
|
||||||
if (!$document) {
|
if (!$document) {
|
||||||
$create_uri = '/phriction/edit/?slug='.$slug;
|
$create_uri = '/phriction/edit/?slug='.$slug;
|
||||||
|
|
||||||
|
@ -112,6 +114,7 @@ class PhrictionDocumentController
|
||||||
'<div class="phriction-header">'.
|
'<div class="phriction-header">'.
|
||||||
$button.
|
$button.
|
||||||
'<h1>'.phutil_escape_html($page_title).'</h1>'.
|
'<h1>'.phutil_escape_html($page_title).'</h1>'.
|
||||||
|
$breadcrumbs.
|
||||||
'</div>'.
|
'</div>'.
|
||||||
$page_content;
|
$page_content;
|
||||||
|
|
||||||
|
@ -124,4 +127,57 @@ class PhrictionDocumentController
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function renderBreadcrumbs($slug) {
|
||||||
|
|
||||||
|
$ancestor_handles = array();
|
||||||
|
$ancestral_slugs = PhrictionDocument::getSlugAncestry($slug);
|
||||||
|
$ancestral_slugs[] = $slug;
|
||||||
|
if ($ancestral_slugs) {
|
||||||
|
$empty_slugs = array_fill_keys($ancestral_slugs, null);
|
||||||
|
$ancestors = id(new PhrictionDocument())->loadAllWhere(
|
||||||
|
'slug IN (%Ls)',
|
||||||
|
$ancestral_slugs);
|
||||||
|
$ancestors = mpull($ancestors, null, 'getSlug');
|
||||||
|
|
||||||
|
$ancestor_phids = mpull($ancestors, 'getPHID');
|
||||||
|
$handles = array();
|
||||||
|
if ($ancestor_phids) {
|
||||||
|
$handles = id(new PhabricatorObjectHandleData($ancestor_phids))
|
||||||
|
->loadHandles();
|
||||||
|
}
|
||||||
|
|
||||||
|
$ancestor_handles = array();
|
||||||
|
foreach ($ancestral_slugs as $slug) {
|
||||||
|
if (isset($ancestors[$slug])) {
|
||||||
|
$ancestor_handles[] = $handles[$ancestors[$slug]->getPHID()];
|
||||||
|
} else {
|
||||||
|
$handle = new PhabricatorObjectHandle();
|
||||||
|
$handle->setName(PhrictionDocument::getDefaultSlugTitle($slug));
|
||||||
|
$handle->setURI(PhrictionDocument::getSlugURI($slug));
|
||||||
|
$ancestor_handles[] = $handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$breadcrumbs = array();
|
||||||
|
foreach ($ancestor_handles as $ancestor_handle) {
|
||||||
|
$breadcrumbs[] = $ancestor_handle->renderLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = phutil_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => '/phriction/',
|
||||||
|
),
|
||||||
|
'Document Index');
|
||||||
|
|
||||||
|
return
|
||||||
|
'<div class="phriction-breadcrumbs">'.
|
||||||
|
$list.' · '.
|
||||||
|
'<span class="phriction-document-crumbs">'.
|
||||||
|
implode(" \xC2\xBB ", $breadcrumbs).
|
||||||
|
'</span>'.
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
phutil_require_module('phabricator', 'applications/markup/engine');
|
phutil_require_module('phabricator', 'applications/markup/engine');
|
||||||
|
phutil_require_module('phabricator', 'applications/phid/handle');
|
||||||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||||
phutil_require_module('phabricator', 'applications/phriction/controller/base');
|
phutil_require_module('phabricator', 'applications/phriction/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/phriction/storage/content');
|
phutil_require_module('phabricator', 'applications/phriction/storage/content');
|
||||||
|
|
|
@ -40,12 +40,9 @@ class PhrictionEditController
|
||||||
}
|
}
|
||||||
$content = id(new PhrictionContent())->load($document->getContentID());
|
$content = id(new PhrictionContent())->load($document->getContentID());
|
||||||
} else if ($slug) {
|
} else if ($slug) {
|
||||||
$document = null;
|
|
||||||
if ($slug) {
|
|
||||||
$document = id(new PhrictionDocument())->loadOneWhere(
|
$document = id(new PhrictionDocument())->loadOneWhere(
|
||||||
'slug = %s',
|
'slug = %s',
|
||||||
$slug);
|
$slug);
|
||||||
}
|
|
||||||
|
|
||||||
if ($document) {
|
if ($document) {
|
||||||
$content = id(new PhrictionContent())->load($document->getContentID());
|
$content = id(new PhrictionContent())->load($document->getContentID());
|
||||||
|
@ -56,14 +53,7 @@ class PhrictionEditController
|
||||||
$content = new PhrictionContent();
|
$content = new PhrictionContent();
|
||||||
$content->setSlug($slug);
|
$content->setSlug($slug);
|
||||||
|
|
||||||
$default_title = null;
|
$default_title = PhrictionDocument::getDefaultSlugTitle($slug);
|
||||||
if ($slug) {
|
|
||||||
$parts = explode('/', trim($slug, '/'));
|
|
||||||
$default_title = end($parts);
|
|
||||||
$default_title = str_replace('_', ' ', $default_title);
|
|
||||||
$default_title = ucwords($default_title);
|
|
||||||
}
|
|
||||||
$default_title = nonempty($default_title, 'New Document');
|
|
||||||
$content->setTitle($default_title);
|
$content->setTitle($default_title);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -57,7 +57,6 @@ class PhrictionDocument extends PhrictionDAO {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function normalizeSlug($slug) {
|
public static function normalizeSlug($slug) {
|
||||||
|
|
||||||
// TODO: We need to deal with unicode at some point, this is just a very
|
// TODO: We need to deal with unicode at some point, this is just a very
|
||||||
|
@ -72,9 +71,51 @@ class PhrictionDocument extends PhrictionDAO {
|
||||||
return $slug.'/';
|
return $slug.'/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getDefaultSlugTitle($slug) {
|
||||||
|
$parts = explode('/', trim($slug, '/'));
|
||||||
|
$default_title = end($parts);
|
||||||
|
$default_title = str_replace('_', ' ', $default_title);
|
||||||
|
$default_title = ucwords($default_title);
|
||||||
|
$default_title = nonempty($default_title, 'Untitled Document');
|
||||||
|
return $default_title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSlugAncestry($slug) {
|
||||||
|
$slug = self::normalizeSlug($slug);
|
||||||
|
|
||||||
|
if ($slug == '/') {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$ancestors = array(
|
||||||
|
'/',
|
||||||
|
);
|
||||||
|
|
||||||
|
$slug = explode('/', $slug);
|
||||||
|
array_pop($slug);
|
||||||
|
array_pop($slug);
|
||||||
|
|
||||||
|
$accumulate = '';
|
||||||
|
foreach ($slug as $part) {
|
||||||
|
$accumulate .= $part.'/';
|
||||||
|
$ancestors[] = $accumulate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ancestors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSlugDepth($slug) {
|
||||||
|
$slug = self::normalizeSlug($slug);
|
||||||
|
if ($slug == '/') {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return substr_count($slug, '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function setSlug($slug) {
|
public function setSlug($slug) {
|
||||||
$this->slug = self::normalizeSlug($slug);
|
$this->slug = self::normalizeSlug($slug);
|
||||||
$this->depth = substr_count($this->slug, '/');
|
$this->depth = self::getSlugDepth($slug);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,5 +10,7 @@ phutil_require_module('phabricator', 'applications/phid/constants');
|
||||||
phutil_require_module('phabricator', 'applications/phid/storage/phid');
|
phutil_require_module('phabricator', 'applications/phid/storage/phid');
|
||||||
phutil_require_module('phabricator', 'applications/phriction/storage/base');
|
phutil_require_module('phabricator', 'applications/phriction/storage/base');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
phutil_require_source('PhrictionDocument.php');
|
phutil_require_source('PhrictionDocument.php');
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2011 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 markup
|
||||||
|
*/
|
||||||
|
class PhrictionDocumentTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
|
public function testSlugNormalization() {
|
||||||
|
$slugs = array(
|
||||||
|
'' => '/',
|
||||||
|
'/' => '/',
|
||||||
|
'//' => '/',
|
||||||
|
'/derp/' => 'derp/',
|
||||||
|
'derp' => 'derp/',
|
||||||
|
'derp//derp' => 'derp/derp/',
|
||||||
|
'DERP//DERP' => 'derp/derp/',
|
||||||
|
'a B c' => 'a_b_c/',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($slugs as $slug => $normal) {
|
||||||
|
$this->assertEqual(
|
||||||
|
$normal,
|
||||||
|
PhrictionDocument::normalizeSlug($slug),
|
||||||
|
"Normalization of '{$slug}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSlugAncestry() {
|
||||||
|
$slugs = array(
|
||||||
|
'/' => array(),
|
||||||
|
'pokemon/' => array('/'),
|
||||||
|
'pokemon/squirtle/' => array('/', 'pokemon/'),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($slugs as $slug => $ancestry) {
|
||||||
|
$this->assertEqual(
|
||||||
|
$ancestry,
|
||||||
|
PhrictionDocument::getSlugAncestry($slug),
|
||||||
|
"Ancestry of '{$slug}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSlugDepth() {
|
||||||
|
$slugs = array(
|
||||||
|
'/' => 0,
|
||||||
|
'a/' => 1,
|
||||||
|
'a/b/' => 2,
|
||||||
|
'a////b/' => 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($slugs as $slug => $depth) {
|
||||||
|
$this->assertEqual(
|
||||||
|
$depth,
|
||||||
|
PhrictionDocument::getSlugDepth($slug),
|
||||||
|
"Depth of '{$slug}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/phriction/storage/document');
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/testing/testcase');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('PhrictionDocumentTestCase.php');
|
|
@ -29,3 +29,12 @@
|
||||||
.phriction-content .phriction-link {
|
.phriction-content .phriction-link {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.phriction-breadcrumbs {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phriction-document-crumbs a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue