Pre-1.0. Not intended for public use yet. The API is subject to undocumented change, and these pages may already be wrong about it.

Skip to content

Activity Content ​

Introduction ​

You may display quoted text, structured values, or file details below an activity's headline.

Adding Quoted Text ​

Use an Excerpt body for someone's words or a passage from a document. Define the body on the quoted model in toFeed():

app/Models/Note.phptoFeed()
php
use Storyfeed\Body\Excerpt;
use Storyfeed\FeedEntity;

public function toFeed(): FeedEntity
{
    return FeedEntity::make()
        ->label('Order note')
        ->body(
            Excerpt::make()
                ->text($this->body)
                ->from($this->author->name)
                ->truncated(false),
        );
}
php
use Storyfeed\Body\Excerpt;
use Storyfeed\FeedEntity;

public function toFeed(): FeedEntity
{
    return FeedEntity::make(
        label: 'Order note',
        body: Excerpt::make(
            text: $this->body,
            from: $this->author->name,
            truncated: false,
        ),
    );
}

Here author is the note's author relationship. from() names whose words are being quoted. Set truncated(false) when the body contains the complete text. The attribution appears below the quotation:

ES
Erica Sinclair sent a note about Order #1035
Can I collect this at the counter?
Erica Sinclair

Record the note as the activity's object and the order as its target:

php
use Storyfeed\Facades\Storyfeed;

$note = $order->notes()->create($request->validated());

Storyfeed::activity()
    ->by($request->user())
    ->action('post', $note)
    ->to($order)
    ->publish();

The body belongs to the note's shared entity snapshot. Saving the note can change the text shown on older activities. To preserve text exactly as it was when the event happened, also record it in activity data.

Adding Entity Bodies ​

A body contains an entity's structured content. Define it in the model's toFeed method and render it in your frontend. The body is available wherever the entity appears. With InteractsWithFeed, saving the model refreshes its shared snapshot while recording is enabled. That can change the body shown on older activities too. Use activity data to capture values as they were at the event.

Text and Labelled Values ​

app/Models/Order.php
php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Storyfeed\Concerns\InteractsWithFeed;
use Storyfeed\Contracts\Feedable;
use Storyfeed\Body\Prose;
use Storyfeed\FeedEntity;

class Order extends Model implements Feedable
{
    use InteractsWithFeed;

    public function toFeed(): FeedEntity
    {
        return FeedEntity::make()
            ->label("Order #{$this->reference}")
            ->body(
                Prose::make()
                    ->content($this->instructions)
                    ->title("Order #{$this->reference} instructions"),
            );
    }
}
php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Storyfeed\Concerns\InteractsWithFeed;
use Storyfeed\Contracts\Feedable;
use Storyfeed\Body\Prose;
use Storyfeed\FeedEntity;

class Order extends Model implements Feedable
{
    use InteractsWithFeed;

    public function toFeed(): FeedEntity
    {
        return FeedEntity::make(
            label: "Order #{$this->reference}",
            body: Prose::make(
                content: $this->instructions,
                title: "Order #{$this->reference} instructions",
            ),
        );
    }
}
SH
Steve Harrington marked Order #1035 ready
Order #1035 instructions

A spoon with the order, please.

Include a title to identify the order when the body appears without a headline.

Use KeyValue for labelled values:

php
use Storyfeed\Body\KeyValue;
use Storyfeed\FeedEntity;

FeedEntity::make()
    ->label("Order #{$this->reference}")
    ->body(
        KeyValue::make()->title("Order #{$this->reference}")->items([ 
            'Pickup' => $this->pickup_at->format('g:i a'),
            'Items' => $this->items->count(),
            'Reference' => KeyValue::verbatim($this->reference),
            'Table' => KeyValue::placeholder($this->table, 'not seated'),
        ]),
    );
php
use Storyfeed\Body\KeyValue;
use Storyfeed\FeedEntity;

FeedEntity::make(
    label: "Order #{$this->reference}",
    body: KeyValue::make( 
        title: "Order #{$this->reference}",
        items: [
            'Pickup' => $this->pickup_at->format('g:i a'),
            'Items' => $this->items->count(),
            'Reference' => KeyValue::verbatim($this->reference),
            'Table' => KeyValue::placeholder($this->table, 'not seated'),
        ],
    ),
);
SH
Order #1035
Pickup
12:10 pm
Items
1
Reference
1035
Table
not seated

Use the KeyValue::placeholder method to specify text for an empty value. Use ->defaultPlaceholder('—') to set the default for every row without its own placeholder, or pass defaultPlaceholder: to KeyValue::make(). The KeyValue::verbatim method marks a value for display without formatting, such as a reference number.

Formatted Text and Raw Output ​

For code or raw output, create the body with Prose::verbatim. It keeps the source characters and line breaks, and long output scrolls within the body:

php
use Storyfeed\Body\Prose;

Prose::verbatim($this->output, title: $this->name); 
BN
Bob Newby wrote Hawkins Lab door override
Hawkins Lab door override
10 REM DOOR OVERRIDE — ILLUSTRATIVE BASIC
20 INPUT "SECURITY CODE"; C$
30 IF C$ = "" THEN GOTO 20
40 PRINT "MANUAL OVERRIDE REQUESTED"
50 FOR D = 1 TO 4
60 PRINT "DOOR"; D; " RELEASE REQUEST SENT"
70 NEXT D
80 END
Hawkins Lab lock status was printed at Hawkins Lab
Hawkins Lab lock status
SYSTEM RESTART ........ COMPLETE
MANUAL INPUT .......... ACCEPTED
DOOR CONTROL .......... ONLINE
EXIT LOCKS ............ RELEASED
DH
Dustin Henderson wrote Cerebro reception log
Cerebro reception log
STATION: CEREBRO / WEATHERTOP
CALL TO UTAH .......... NO REPLY
UNEXPECTED SIGNAL .... VOICE / RUSSIAN
MESSAGE .............. REPEATING
NEXT STEP ............ KEEP THE TAPE

For formatted text, use Prose::markdown or Prose::html. Storyfeed stores the source, and the renderer converts and sanitizes it:

php
use Storyfeed\Body\Prose;

Prose::markdown($this->notes, title: $this->title); 
MB
Murray Bauman wrote Murray’s gate investigation
Murray’s gate investigation

What the machine needs

Alexei’s account: the machine is opening a gate beneath the mall.

  • Locate the control room.
  • Reach the two shutdown keys.
  • Check the safe combination before going in.

Working note: translation is evidence, not a complete floor plan.

Hawkins Lab system report was printed at Hawkins Lab
Hawkins Lab system report

Door control restored. The restart has returned the locks to manual control.

  • Keep the exit route clear.
  • Confirm that everyone has left the building.

Quoting a Source ​

Use Excerpt to quote someone else's words, such as a person interviewed for a story. The from argument names who said them or where they came from:

php
use Storyfeed\Body\Excerpt;
use Storyfeed\FeedEntity;

FeedEntity::make()
    ->label($this->title)
    ->body(
        Excerpt::make() 
            ->text($this->pull_quote)
            ->from($this->pull_quote_source),
    );
php
use Storyfeed\Body\Excerpt;
use Storyfeed\FeedEntity;

FeedEntity::make(
    label: $this->title,
    body: Excerpt::make( 
        text: $this->pull_quote,
        from: $this->pull_quote_source,
    ),
);
NW
They came back every night for my fertilizer, bag after bag
Doris Driscoll

Excerpts are marked as truncated by default. Call truncated(false) when the text is complete. For the entity's own text, such as an article's opening paragraph, use Prose instead.

A record of an answer taken down word for word quotes the person who gave it, and marks the text as complete:

DH
Dustin Henderson wrote Planck’s constant
Planck’s constant is 6.62607004.
Suzie, over Cerebro

Adding an Image ​

Use an Image body to show a photograph with a caption. The body names a feedMedia slot; it never stores the picture's URL. withPreview() selects the preview slot, which is also the default:

app/Models/Photo.phptoFeed()
php
use Storyfeed\Body\Image;
use Storyfeed\FeedEntity;

return FeedEntity::make()
    ->label($this->name)
    ->body(
        Image::make()
            ->caption($this->subject)
            ->alt($this->description)
            ->withPreview()
    );
SH
Steve Harrington added a photo of USS Butterscotch
USS Butterscotch
USS Butterscotch

Use withImage() for the image slot or withIcon() for the icon slot. The renderer uses alt, then the caption, then an empty alt attribute. An empty slot draws nothing, including the caption. See Feed Media for the resolver that supplies the picture.

File Attachment ​

Use FileAttachment in a Document model's toFeed method to describe a PDF, such as a signed agreement:

app/Models/Document.phptoFeed()
php
use Storyfeed\Body\FileAttachment;
use Storyfeed\FeedEntity;

return FeedEntity::make()
    ->label($this->name)
    ->body( 
        FileAttachment::make()
            ->size($this->bytes)
            ->mediaType('application/pdf')
            ->name($this->name)
    );
php
use Storyfeed\Body\FileAttachment;
use Storyfeed\FeedEntity;

return FeedEntity::make(
    label: $this->name,
    body: FileAttachment::make( 
        size: $this->bytes,
        mediaType: 'application/pdf',
        name: $this->name,
    ),
);
SH

135 KB · application/pdf

The FileAttachment body stores file details. Configure the URL separately with the link resolver.

Lists of Items ​

Use ItemList for an order's items. Each item may be a plain string or a FeedLink to another page:

php
use App\Models\OrderLine;
use Storyfeed\Body\ItemList;
use Storyfeed\FeedEntity;
use Storyfeed\FeedLink;

FeedEntity::make()
    ->label("Order #{$this->reference}")
    ->body(
        ItemList::make()
            ->title("Order #{$this->reference} items")
            ->items(
                $this->lines->take(2)->map(
                    fn (OrderLine $line) => FeedLink::make( 
                        $line->item->name,
                        $line->item->url,
                    ),
                ),
            )
            ->items([$this->lines->get(2)->item->name])
            ->totalItems($this->lines->count())
            ->more(FeedLink::make("Order #{$this->reference}", $this->url)),
    );
php
use App\Models\OrderLine;
use Storyfeed\Body\ItemList;
use Storyfeed\FeedEntity;
use Storyfeed\FeedLink;

FeedEntity::make(
    label: "Order #{$this->reference}",
    body: ItemList::make(
        title: "Order #{$this->reference} items",
        items: [
            ...$this->lines->take(2)->map(
                fn (OrderLine $line) => FeedLink::make( 
                    $line->item->name,
                    $line->item->url,
                ),
            ),
            $this->lines->get(2)->item->name,
        ],
        totalItems: $this->lines->count(),
        more: FeedLink::make("Order #{$this->reference}", $this->url),
    ),
);
KW
Order #1042 items
2 moreOrder #1042

This order has five items. The body includes two linked items and one plain-string item. The totalItems method records the full count, and more provides a link to the order containing the remaining items. Use ItemList::ordered() when the sequence of the items matters.

A list can also preserve a short arrangement of items:

JB
Joyce Byers wrote Joyce’s alphabet wall
Joyce’s alphabet wall
  • A B C D E F G H
  • I J K L M N O P Q
  • R S T U V W X Y Z

Adding Multiple Bodies ​

Entities in any role can have bodies. Your frontend chooses which to display. Each body() call appends a body in the order given:

app/Models/MenuItem.php
php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Storyfeed\Body\KeyValue;
use Storyfeed\Body\Prose;
use Storyfeed\Concerns\InteractsWithFeed;
use Storyfeed\Contracts\Feedable;
use Storyfeed\FeedEntity;

class MenuItem extends Model implements Feedable
{
    use InteractsWithFeed;

    public function toFeed(): FeedEntity
    {
        return FeedEntity::make()
            ->label($this->name)
            ->body(Prose::make($this->description))
            ->body(KeyValue::make()->items('Station', $this->station));
    }
}
php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Storyfeed\Body\KeyValue;
use Storyfeed\Body\Prose;
use Storyfeed\Concerns\InteractsWithFeed;
use Storyfeed\Contracts\Feedable;
use Storyfeed\FeedEntity;

class MenuItem extends Model implements Feedable
{
    use InteractsWithFeed;

    public function toFeed(): FeedEntity
    {
        return FeedEntity::make(
            label: $this->name,
            body: [
                Prose::make($this->description),
                KeyValue::make(items: ['Station' => $this->station]),
            ],
        );
    }
}

Linking a Title ​

Use MediaObject for a notice with a title and a short description. Pass a FeedLink as its subject to make the title a link:

php
use Storyfeed\Body\MediaObject;
use Storyfeed\FeedEntity;
use Storyfeed\FeedLink;

FeedEntity::make()
    ->label($this->title)
    ->body(
        MediaObject::make()
            ->subject(FeedLink::make($this->title, $this->url)) 
            ->content($this->description),
    );
php
use Storyfeed\Body\MediaObject;
use Storyfeed\FeedEntity;
use Storyfeed\FeedLink;

FeedEntity::make(
    label: $this->title,
    body: MediaObject::make(
        subject: FeedLink::make($this->title, $this->url), 
        content: $this->description,
    ),
);
SH

Scoops Ahoy opening hours

The counter opens at 10 am. Orders are available until 9 pm.

The title links to the notice at the URL supplied when its body is stored.

To link to another page, pass that page's title and URL:

php
use Storyfeed\Body\MediaObject;
use Storyfeed\FeedEntity;
use Storyfeed\FeedLink;

FeedEntity::make()
    ->label($this->title)
    ->body(
        MediaObject::make()
            ->subject(FeedLink::make($this->guide_title, $this->guide_url)) 
            ->content($this->description),
    );
php
use Storyfeed\Body\MediaObject;
use Storyfeed\FeedEntity;
use Storyfeed\FeedLink;

FeedEntity::make(
    label: $this->title,
    body: MediaObject::make(
        subject: FeedLink::make($this->guide_title, $this->guide_url), 
        content: $this->description,
    ),
);
SH

Starcourt Mall visitor guide

The visitor guide includes entrances, parking and shop locations.

The body title links to the visitor guide, while the headline links to the notice. A plain-string subject displays a title without a link.

A FeedLink contains a label and an href. Pass the destination URL as the second argument to FeedLink::make, or set it with the href method.

The href is stored as written. It can become stale if a route changes or a signed URL expires.

The label names the thing, such as a notice or an order. It should not be an instruction such as “Open the conversation”. See the FeedLink reference for its methods and the body fields that accept it.

Available Body Types ​

Body TypeContentPayload Keys
KeyValuelabelled valuestitle, defaultPlaceholder, items[] of key, value, verbatim, placeholder
Excerpta quoted passage and its sourcetext, from, truncated
Imagea picture and captioncaption, alt, width, height, image (slot name)
FileAttachmentfile name, size, and media typename, size, mediaType
Prosetext and its formatcontent, mediaType, verbatim, title
ItemListnamed items with optional linkstitle, items[], ordered, totalItems, more
MediaObjecta title, text, image, and filessubject, content, image, files, footnote
Componenta custom component name and propsname, props

These classes use the Storyfeed\Body namespace. Each body's payload includes its type in $body, such as Storyfeed/Body/KeyValue, and its version in $v. Your renderer uses these fields to display the body. Passing a string as a body creates a Prose body.

See Resolving Bodies When Retrieved for current and deferred values, or Custom Body Types to render custom components and define your own body types.

Released under the MIT License.