diff --git a/content/collections/sections/seo-pro.md b/content/collections/sections/seo-pro.md new file mode 100644 index 000000000..c07f24bfb --- /dev/null +++ b/content/collections/sections/seo-pro.md @@ -0,0 +1,7 @@ +--- +id: b2dfa5a7-326a-462f-bfa4-f6a06b589c3a +blueprint: sections +title: 'SEO Pro' +template: seo-pro.index +section: seo_pro +--- diff --git a/content/collections/seo_pro.yaml b/content/collections/seo_pro.yaml new file mode 100644 index 000000000..3068880b2 --- /dev/null +++ b/content/collections/seo_pro.yaml @@ -0,0 +1,18 @@ +title: 'SEO Pro' +template: page +layout: layout +mount: b2dfa5a7-326a-462f-bfa4-f6a06b589c3a +revisions: false +route: 'seo-pro/{slug}' +sort_dir: asc +date_behavior: + past: public + future: private +preview_targets: + - + label: Entry + url: '{permalink}' + refresh: true +inject: + blueprint: page + section: seo_pro diff --git a/content/collections/seo_pro/advanced-configuration.md b/content/collections/seo_pro/advanced-configuration.md new file mode 100644 index 000000000..d18046f69 --- /dev/null +++ b/content/collections/seo_pro/advanced-configuration.md @@ -0,0 +1,46 @@ +--- +id: 1917f8a1-4b89-410b-ba3d-20d85bcc9ded +blueprint: seo_pro +title: 'Advanced Configuration' +--- +## Publishing Config + +You can publish SEO Pro's config for modification by running the following: + +```bash +php artisan vendor:publish --tag="seo-pro-config" +``` + +## Publishing Views + +You can publish SEO Pro's `sitemap.xml` and `humans.txt` views for modification by running the following: + +```bash +php artisan vendor:publish --tag="seo-pro-views" +``` + +These views will be published into your `resources/views/vendor/seo-pro` directory for modification. + +You may also override the default `meta.antlers.html` view, though it is not published by default. ***Important Note***: Overriding this view will require you to be mindful of updates as it will not be automatically maintained for you. + +## Sitemap.xml + +A `sitemap.xml` route is automatically generated for you. + +If you disable SEO on the section or item level, the relevant section/item will automatically be discluded from the sitemap. + +If you wish to completely disable the sitemap, change it's URL, or customize it's cache expiry, you can [publish the SEO Pro config](/seo-pro/advanced-configuration) and modify these settings within `config/statamic/seo-pro.php`. + +If you wish to customize the contents of the `sitemap.xml` view, you may also [publish the SEO Pro views](/seo-pro/advanced-configuration#publishing-views) and modify the provided antlers templates within your `resources/views/vendor/seo-pro` folder. + +## Pagination Meta + +By default, `canonical` URL meta will show pagination on `?page=2` and higher, with `rel="prev"` / `rel="next"` links when appropriate. + +If you wish to customize or disable pagination, you can [publish the SEO Pro config](/seo-pro/advanced-configuration) and modify these settings within `config/statamic/seo-pro.php`. + +## Twitter Card Meta + +By default, `twitter:card` meta will be rendered using `summary_large_image`. + +If you wish to change this to `summary`, you can [publish the SEO Pro config](/seo-pro/advanced-configuration) and modify your twitter card within `config/statamic/seo-pro.php`. \ No newline at end of file diff --git a/content/collections/seo_pro/available-commands.md b/content/collections/seo_pro/available-commands.md new file mode 100644 index 000000000..e83999e04 --- /dev/null +++ b/content/collections/seo_pro/available-commands.md @@ -0,0 +1,79 @@ +--- +id: c7b6e4b0-15ac-4301-842f-61cabf36c1e7 +blueprint: seo_pro +title: 'Available Commands' +intro: 'The following commands are available to assist with setup or managing entry link metadata, keywords, and embeddings.' +--- +## `seo-pro:links-setup` + +Asssists with configuring Content Linking features on a new installation. For more information, reading [reading the configuration steps](/seo-pro/content-linking-setup#using-the-seoprolinkssetup-command) within the [Setup & Config](/seo-pro/content-linking-setup) guide. + +To run this command, run the following from the root of your project: + +```bash +php please seo-pro:links-setup +``` + +## `seo-pro:analyze-content` + +The `analyze-content` command may be used to crawl and generate the required records for each entry within your site. + +To run this command, run the following from the root of your project: + +```bash +php please seo-pro:analyze-content +``` + +This command has the same effect as running the following commands, in this order: + +```bash +php please seo-pro:scan-links +php please seo-pro:generate-keywords +php please seo-pro:generate-embeddings +``` + +## `seo-pro:generate-embeddings` + +The `generate-embeddings` command may be used to create records within the `seopro_entry_embeddings` database table. This command will call the configured embeddings API. + +To create embeddings, run the following from the root of your project: + +```bash +php please seo-pro:generate-embeddings +``` + +This command will *not* regenerate embeddings if the configuration has not changed, or if the entry's content has not changed. + +## `seo-pro:generate-keywords` + +The `generate-keywords` command may be used to generate records within the `seopro_entry_keywords` database table. + +To generate entry keywords, run the following from the root of your project: + +```bash +php please seo-pro:generate-keywords +``` + +This command will *not* regenerate keywords if the configuration has not changed, or if the entry's content has not changed. + +## `seo-pro:scan-links` + +The `scan-links` command may be used to generate records within the `seopro_entry_links` database table. This command will crawl all eligible entries within your sites and parse their content. + +To run this command, run the following from the root of your project: + +```bash +php please seo-pro:scan-links +``` + +This command will *not* regenerate keywords if the configuration has not changed, or if the entry's content has not changed. + +## `seo-pro:sync-entry-links` + +SEO Pro stores some entry metadata, such as the title and URI, within the `seopro_entry_links` database table for performance reasons. If you are making changes directly within flat-files, this metadata can become out of sync. + +To sync entry metadata, run the following from the root of your project: + +```bash +php please seo-pro:sync-entry-links +``` \ No newline at end of file diff --git a/content/collections/seo_pro/available-hooks.md b/content/collections/seo_pro/available-hooks.md new file mode 100644 index 000000000..b89473f00 --- /dev/null +++ b/content/collections/seo_pro/available-hooks.md @@ -0,0 +1,33 @@ +--- +id: 41cd0830-04f4-43bd-875e-46f0d608be9e +blueprint: seo_pro +title: 'Available Hooks' +intro: 'Hooks are a powerful extension feature of Statamic, and SEO Pro provides a few Content Linking hooks. To learn more about Hooks, consider reading [their associated documentation](/extending/hooks).' +--- +## Link Manager Index Query: `query` + +Triggered before the index query for the Link Manager table is executed. The payload will be an object with a `query` property. + +```php +use Statamic\SeoPro\Hooks\CP\EntryLinksIndexQuery; + +EntryLinksIndexQuery::hook('query', function ($payload, $next) { + $payload->query; // a QueryBuilder instance. + + return $next($payload); +}); +``` + +## Global Automatic Links Index Query: `query` + +Triggered before the index query for the Global Automatic Links table is executed. The payload will be an object with a `query` property. + +```php +use Statamic\SeoPro\Hooks\CP\AutomaticLinksIndexQuery; + +AutomaticLinksIndexQuery::hook('query', function ($payload, $next) { + $payload->query; // a QueryBuilder instance. + + return $next($payload); +}); +``` \ No newline at end of file diff --git a/content/collections/seo_pro/collection-configuration.md b/content/collections/seo_pro/collection-configuration.md new file mode 100644 index 000000000..cc99724f0 --- /dev/null +++ b/content/collections/seo_pro/collection-configuration.md @@ -0,0 +1,73 @@ +--- +id: 245b78ff-2860-473d-a700-8c1eaed30d39 +blueprint: seo_pro +title: 'Collection Configuration' +intro: 'In addition to the [core Content Linking configuration](/seo-pro/content-linking-setup#seo-pro-configuration-file-updates), SEO Pro allows you to fine-tune linking behavior for each collection as well as decide which collections should not be analyzed.' +--- +## Disabling Content Linking features for collections + +Depending on how much content your site has, you may wish to disable Content Linking features on some collections for performance reasons or disable analysis on sensitive collections, such as products, orders, or other similar types of data. + +There are two ways to disable collections: by updating the PHP configuration file and within the Control Panel. + +### Disabling collections within the config file + +To disable collections using the PHP configuration file, add the collection handles to the `linking.disabled_collections` value within the configuration file. + +Collections disabled this way will *not* be used when generating embeddings and keywords, or when suggesting related content, even if someone somehow enables them through the Control Panel. + +```php + [ + + // Other linking configuration. + + 'disabled_collections' => [ + 'products', + 'orders', + 'feedback', + ], + + ], +]; +``` + + +### Disabling collections within the Control Panel + +The second method is through the Statamic Control Panel. From within the SEO Pro Link Manager, activate the dropdown in the upper right-hand corner of the screen to reveal additional options. From the options that appear, select "Collection Linking Behavior": + +![Locating Collection Linking Behavior](/img/seo-pro/collection-linking-behavior.png) + +From the list of available collections, activate the actions dropdown menu to the right of the collection to update and select "Edit Linking Behavior": + +![Editing collection linking behavior](/img/seo-pro/edit-collection-linking-behavior.png) + +On the panel that appears, adjust the "Linking Enabled" option to the desired value and click "Save": + +![Collection Linking Enabled Setting](/img/seo-pro/collection-allow-linking-toggle.png) + +## Restricting collection suggestions + +Additionally, SEO Pro allows you to determine *which* collections should be considered when preparing link suggestions. For example, if a site contains a `blog`, `articles`, `pages`, and `knowledge_base` collection, you could configure the `blog` collection to only receive suggestions from the `blog` and `articles` collections, ignoring any other collection. Similarly, if you are using [Multi-site](/multi-site), you can disable suggestions across sites. + +On the Collection Linking Behavior page, select the "Edit Linking Behavior" option from the actions menu next to the collection you'd like to update. On the stack panel that appears, you may adjust the following values: + +* **Linking Enabled**: Determines if content linking is enabled for the collection. +* **Allow Cross-Site Suggestions**: Determines if SEO Pro should suggest link suggestions across sites when using Multi-site. +* **Allow Suggestions from All Collections**: When toggled to `false`, you will be able to select individual collections that will be used when retrieving suggested content or looking for linking opportunities. + +![Restricting collection suggestions](/img/seo-pro/restricting-collection-suggestions.png) + +Collection configuration changes made within the Control Panel are stored within the `seopro_collection_link_settings` database table. + +## Resetting collection configuration + +To reset a collection's configuration to its default values, select the "Reset Collection Settings" from the actions drop-down menu to the right of the desired collection: + +![Resetting collection settings](/img/seo-pro/resetting-collection-settings.png) \ No newline at end of file diff --git a/content/collections/seo_pro/content-linking-faq.md b/content/collections/seo_pro/content-linking-faq.md new file mode 100644 index 000000000..104bdd467 --- /dev/null +++ b/content/collections/seo_pro/content-linking-faq.md @@ -0,0 +1,112 @@ +--- +id: 2efde67f-35b2-40fd-b54b-b53fb84e7c3a +blueprint: seo_pro +title: 'Content Linking FAQ and Troubleshooting' +nav_title: FAQs/Troubleshooting +intro: 'Answers to common questions and troubleshooting tips.' +--- +## What are the *minimum* steps to setup Content Linking? + +After installing SEO Pro, the following are the minimum steps: + +1. Ensure a `default` database connection has been configured +2. Publish and run SEO Pro's database migrations +3. Add an OpenAI API key to the `.env` by adding the `SEO_PRO_OPENAI_API_KEY=` entry +4. Enable Content Linking by adding `SEO_PRO_LINKING_ENABLED=true` to the `.env` file +5. Run the `php please seo-pro:analyze-content` command from the root of the project + +While this is the *fastest* way to get started, you may want to review the documentation for [disabling collections first](/seo-pro/collection-configuration). + +## Can I use Content Linking features *without* AI embeddings or OpenAI? + +The content linking features cannot be used without generating embeddings from entry content. + +Any OpenAI compatible embeddings API may be used. SEO Pro does not have a hard dependency on OpenAI, specifically. + +## Why are manual changes to flat-file entries not reflected in the Link Manager? + +Some information is cached within the `seopro_entry_links` database table for performance reasons. Entry titles and URIs can be synchronized by running the following command from the root of the project: + +```bash +php please seo-pro:sync-entry-links +``` + +This command will also delete rows from the `seopro_entry_links` table if the entry no longer exists. + +## How do I exclude a specific entry from being suggested? + +To exclude a specific entry from being suggested, follow the [steps outlined here](/seo-pro/entry-configuration#preventing-the-entry-from-being-suggested). + +## Some collections are not appearing in suggested results + +Collections not appearing in results can be caused by a variety of things: + +* The collections are listed in the `statamic.seo-pro.linking.disabled_collections` configuration option within the `config/statamic/seo-pro.php` configuration file +* The collections are not listed in any other collection's "Limit Suggestions From" configuration +* The current user does not have permissions to view entries from the collections that are not appearing +* The collection was added after initially setting up SEO Pro content linking features and keywords, link metadata, or embeddings have not been generated for that collection yet + +## The site's name, branding, or other repeatedly used phrased are appearing too often in suggestions. + +Phrases that are appear frequently within content, but provide no real value in terms of linking opportunities, should be added to each site's "Ignored Phrases" list. This list is added to the keyword and phrases "stop word" list. + +Updates to a site's ignored phrases list *are applied* when returning suggestions for specific entries. Because of this, the `seopro_entry_keywords` table does *not* need to be regenerated after making changes. + +## No entries are appearing in the Link Manager + +Entries not appearing within the Link Manager is most often caused by an empty `seopro_entry_links` database table. The simplest way to resolve this is by running the following command from the root of the project: + +```bash +php please seo-pro:analyze-content +``` + +The `seo-pro:analyze-content` command will work to regenerate keywords, embeddings, and link meta data for all missing entries, or if the content has changed. If you'd like to target the `seopro_entry_links` table specifically, you may run the following command instead: + +```bash +php please seo-pro:scan-link +``` + +## Entries are present in the Link Manager, but no suggestions are appearing + +The most common reason for no suggestions appearing is missing embeddings or keyword data. This can be regenerated by running the following command from the root of the project: + +```bash +php please seo-pro:analyze-content +``` + +If both tables are already populated, and suggestions are still not appearing for an entry, this may be caused by any of the following: + +* There are missing values in the `seopro_entry_links` database table. Run `php please seo-pro:scan-links` to resolve this. +* No more suggestions could be found within the keyword or related entry threshold limits. If a link already appears within the content, that entry will *not* be suggested again. +* It is possible the content crawler is unable to find any content to parse. If pages do not contain `
` tags, a developer will need to use the `{{ seo_pro:content }}{{ /seo_pro:content }}` tag pair to mark the content to be parsed and analyzed. +* A network or database related issue could be interfering with generating suggestions. This is possible on very large sites; adjusting which collections an entry can receive suggestions from may help if this is the case. + +## I have a custom fieldtype and links are not automatically inserted into nested fields. + +Content linking and automatic link insertion relies on an internal concept of "content mapping". SEO Pro utilizes this content mapping system to target specific fields and instruct the insertion/replacement engine on how to perform the content updates. + +Because of the varied nature of custom fieldtypes, SEO Pro's content mapper cannot make generalizations on how the content is structured, or how to update it in a safe way. For these reasons, custom fieldtypes would require their own custom content mapper and link insertion implementation. + +## Analyzing content is taking a long time. + +SEO Pro must retrieve the content and interact with third-party APIs, as well as extract keywords and phrases for each entry, which can take some time. To reduce the number of entries processed, consider restricting which collections are analyzed. + +## How do I customize the format of links inserted into Text, Textarea, and Markdown fieldtypes? + +SEO Pro uses templates when creating the link to insert into your site's output. You can override these templates by publishing the associated views with the following command: + +```bash + php artisan vendor:publish --tag=seo-pro-linking-views +``` + +After the command runs, you will find three new templates within your resources directory: + +* `vendor/seo-pro/links/automatic.antlers.html`: Determines the format of Global Automatic Links +* `vendor/seo-pro/links/html.antlers.html`: Determines the format of links inserted into [Text](/fieldtypes/text) and [Textarea](/fieldtypes/textarea) fieldtypes +* `vendor/seo-pro/links/markdown.antlers.html`: Determines the format of links inserted into the [Markdown](/fieldtypes/markdown) fieldtypes + +Modify the template associated with the fieldtype(s) you would like to adjust the format of. + +## How do I disable content mapping for a specific entry field? + +You may disable content mapping for a specific entry field [by following the steps listed in this guide](/seo-pro/parsing-content-and-content-mapping#disabling-content-mapping-for-a-field). \ No newline at end of file diff --git a/content/collections/seo_pro/content-linking-permissions.md b/content/collections/seo_pro/content-linking-permissions.md new file mode 100644 index 000000000..8a1463015 --- /dev/null +++ b/content/collections/seo_pro/content-linking-permissions.md @@ -0,0 +1,20 @@ +--- +id: 33fbfb5a-5f44-4a58-a923-ab1f4ea310a0 +blueprint: seo_pro +title: 'Content Linking Permissions' +nav_title: Permissions +--- +SEO Pro's content linking features provide additional user permissions and interact with existing permissions. + +The following existing permissions will be considered when displaying links within the Link Manager, or when suggesting links or content: + +* Only collections a user has access to will be available. +* Only sites a user has access to will be available. +* Editing Permissions: If a user cannot edit an entry, they will not be able to insert links or edit entries from the Link Manager. + +In addition to interacting with existing permissions, the following additional permissions are available: + +* **Manage Links**: Determines if the user will have access to any content linking features within the Control Panel. When revoked, the user will not be able to view the Link Manager. + * **Edit Collection Linking Behavior**: Determines if the user can update collection linking behavior settings. + * **Edit Site Linking Behavior**: Determines if the user can update site linking behavior settings. + * **Edit Global Automatic Links**: Determines if the user can add/create/delete Global Automatic Links. \ No newline at end of file diff --git a/content/collections/seo_pro/content-linking-setup.md b/content/collections/seo_pro/content-linking-setup.md new file mode 100644 index 000000000..98f2db45f --- /dev/null +++ b/content/collections/seo_pro/content-linking-setup.md @@ -0,0 +1,316 @@ +--- +id: 44e6499b-8232-47ff-8853-3d2b2c82e565 +blueprint: seo_pro +title: 'Content Linking Setup & Config' +nav_title: 'Setup & Config' +intro: |- + SEO Pro can help uncover internal linking opportunities across your entries, as well as provide reporting on existing links within your content. + + SEO Pro scans the content of your site and uses artificial intelligence (AI) to create unique embeddings for each of your entries, enabling it to identify related content. Once related content has been found for any given entry, SEO Pro dives deeper, analyzing the content for matching keywords and phrases. + + The more related two entries are, and the more keywords and phrases that overlap, the stronger a suggested link opportunity is. You are in charge of accepting or rejecting all internal link suggestions. +--- +Once SEO Pro has been installed, you can begin setting up Content Linking. Content Linking *requires* a database to function, even if the rest of your site is using flat-files. + +The simplest way to get started is by using SQLite in your local environment, but you may wish to use another database engine. If you need help with setting up your database connection, consider reading through Laravel's documentation on this topic: [https://laravel.com/docs/database](https://laravel.com/docs/database). + +:::tip +While SEO Pro content linking utilizes embeddings, a vector database engine is *not* required in order to use its features. +::: + +Content linking also requires an OpenAI compatible embeddings API. You are responsible for API costs incurred by the use of SEO Pro features. If your site contains a lot of content, you may wish to experiment with different collection and AI model configurations, as well as setup cost control systems within your preferred AI service provider. + +The following information is required before continuing: + +1. **OpenAI API Key**: An OpenAI API key that will be used to make API requests. If you are using alternate services, please consult their documentation on what may be required. For example, if you are using something like Ollama locally, you may use any value. +2. **OpenAI API Base URI**: If you plan to use the OpenAI API, the default value of "api.openai.com/v1" will be used. If using another service, you will need to locate the API base URI of that service before proceeding. If using Ollama locally, the API base URI might look something like `http://localhost:11434/v1`. +3. **OpenAI Embeddings Model Name**: The name of the model that should be used when generating embeddings for your content. + +By default, when using the OpenAI API, SEO Pro will use the `text-embedding-3-small` model. You can find other available models at [https://platform.openai.com/docs/guides/embeddings/embedding-models](https://platform.openai.com/docs/guides/embeddings/embedding-models). You are encouraged to experiment with a subset of your content to determine the best model for your use-case. + +If you *not* using the OpenAI API, you are still required to provide a valid model name. Consult the documentation for your preferred service or platform to learn how to configure or retrieve AI model names. + +## Using the `seo-pro:links-setup` command + + +Verify you have completed the following or have access to the required information before continuing: + +* A default database connection has been setup and configured *and* is ready to be used +* You have access to an OpenAI (or compatible) API Key +* The API base URI, if using an OpenAI alternative +* The AI embedding model name. This is required if you are *not* using the OpenAI API *or* if you already know you want to use a model other than `text-embedding-3-small` when using the OpenAI API + +The simplest way to setup content linking is by running the following command from the root of your project: + +```bash +php please seo-pro:links-setup +``` + + +You will be prompted to verify your database setup and provide your API information. + +The following is an example of what this process might look like when using the OpenAI API: + +```text +> php please seo-pro:links-setup +Content Linking Setup + + Do you have a database connection configured? (yes/no) [no]: + > yes + + Would you like to publish and run the content linking migrations? (yes/no) [yes]: + > yes + + INFO Publishing [seo-pro-migrations] assets. + + <> + + Would you like to configure your OpenAI API access? (yes/no) [yes]: + > yes + + Will you be using an alternative, OpenAI compatible API? (yes/no) [no]: + > no + + Please enter the API key to make requests: + > super-secret-api-key + + What AI model would you like to use? [text-embedding-3-small]: + > text-embedding-3-small + + Would you like to enable content linking? (yes/no) [yes]: + > yes +``` + +Alternatively, the process is a bit longer if you are using an OpenAI alternative. The following is example output of configuring SEO Pro content linking to use a local Ollama instance: + +```text +> php please seo-pro:links-setup +Content Linking Setup + + Do you have a database connection configured? (yes/no) [no]: + > yes + + Would you like to publish and run the content linking migrations? (yes/no) [yes]: + > yes + + INFO Publishing [seo-pro-migrations] assets. + + <> + + Would you like to configure your OpenAI API access? (yes/no) [yes]: + > yes + + Will you be using an alternative, OpenAI compatible API? (yes/no) [no]: + > yes + + What is the base URL for the API you will be using?: + > http://localhost:11434/v1 + + Please enter the API key to make requests: + > some-random-string + + What AI model would you like to use? [text-embedding-3-small]: + > nomic-embed-text:latest + + Would you like to enable content linking? (yes/no) [yes]: + > yes +``` + +## Manually updating the `.env` file + + +The following environment variables can be set to configure your site's OpenAI API access and model details: + +```env +SEO_PRO_OPENAI_BASE_URI= +SEO_PRO_OPENAI_API_KEY= +SEO_PRO_OPENAI_MODEL= +SEO_PRO_LINKING_ENABLED= +``` + +If you'd like to access any of these values within custom code, the following configuration keys may be used: + +|.env Value | Configuration Key | +|---|---| +| SEO_PRO_OPENAI_BASE_URI | `statamic.seo-pro.linking.openai.base_uri` | +| SEO_PRO_OPENAI_API_KEY | `statamic.seo-pro.linking.openai.api_key`| +| SEO_PRO_OPENAI_MODEL | `statamic.seo-pro.linking.openai.model` | +| SEO_PRO_EMBEDDING_TOKEN_LIMIT | `statamic.seo-pro.linking.openai.token_limit` | +| SEO_PRO_LINKING_ENABLED | `statamic.seo-pro.linking.enabled` | + +## SEO Pro Configuration file updates + + +If you'd like to manage content linking configuration via. the `config/statamic/seo-pro.php` configuration file, you may publish SEO Pro's configuration file by running the following command from the root of your project: + +```bash +php artisan vendor:publish --tag="seo-pro-config" +``` + +### Updating an existing configuration file + +If you are already using SEO Pro and have previously published the configuration file, you can update your existing configuration file with the following content. + +```php + [ + 'connection' => env('SEO_PRO_JOB_CONNECTION'), + 'queue' => env('SEO_PRO_JOB_QUEUE'), + ], + + 'linking' => [ + + 'enabled' => env('SEO_PRO_LINKING_ENABLED', false), + + 'openai' => [ + 'base_uri' => env('SEO_PRO_OPENAI_BASE_URI', 'api.openai.com/v1'), + 'api_key' => env('SEO_PRO_OPENAI_API_KEY'), + 'model' => env('SEO_PRO_OPENAI_MODEL', 'text-embedding-3-small'), + 'token_limit' => env('SEO_PRO_EMBEDDING_TOKEN_LIMIT', 8000), + ], + + 'keyword_threshold' => 65, + + 'prevent_circular_links' => false, + + 'internal_links' => [ + 'min_desired' => 3, + 'max_desired' => 6, + ], + + 'external_links' => [ + 'min_desired' => 0, + 'max_desired' => 3, + ], + + 'suggestions' => [ + 'result_limit' => 10, + 'related_entry_limit' => 20, + ], + + 'rake' => [ + 'phrase_min_length' => 0, + 'filter_numerics' => true, + ], + + 'drivers' => [ + 'embeddings' => \Statamic\SeoPro\Linking\Embeddings\OpenAiEmbeddings::class, + 'keywords' => \Statamic\SeoPro\Linking\Keywords\Rake::class, + 'tokenizer' => \Statamic\SeoPro\Content\Tokenizer::class, + 'content' => \Statamic\SeoPro\Content\ContentRetriever::class, + 'link_scanner' => \Statamic\SeoPro\Linking\Links\LinkCrawler::class, + ], + + 'disabled_collections' => [ + ], + + ], +]; +``` + +### Configuration option reference + +There are many configuration options available, and some serve as defaults that can easily be re-used if you manage multiple Statamic sites. + +#### `statamic.seo-pro.jobs.*` + +Allows you to specify which queue, if any, should be used for SEO Pro jobs. + +#### `statamic.seo-pro.linking.enabled` + +Determines if Content Linking features are enabled. When set to `false`, the Link Manager and related features will *not* appear in the Control Panel. + +#### `statamic.seo-pro.linking.openai.base_uri` + +The base URI to use when making OpenAI embedding API requests. + +#### `statamic.seo-pro.linking.openai.api_key` + +The API key to use when making OpenAI embedding API requests. + +#### `statamic.seo-pro.linking.openai.model` + +The AI model to use to generate embeddings from entry content. + +#### `statamic.seo-pro.linking.openai.token_limit` + +Some AI models have a limit to how many tokens they can accept. Adjust this setting if you are using a model that has a lower token limit than the default of `8000`. + +#### `statamic.seo-pro.linking.keyword_threshold` + +A threshold value that can be used to filter suggested entries based on how relevant keyword and phrases are to the target entry. + +A lower value will result in *more* suggestions, but may reduce the quality of the matches. Lower ranked matches may also not be able to be linked automatically as they may not share exact keyword or phrase matches. + +This configuration value is a default for Sites, and can be modified by users within the Control Panel. + +#### `statamic.seo-pro.linking.prevent_circular_links` + +When enabled, SEO Pro will attempt to prevent circular link suggestions. + +A circular link suggestion would occur if Entry B is suggested for Entry A, but Entry B already links to Entry A. + +This configuration value is a default for Sites, and can be modified by users within the Control Panel. + +#### `statamic.seo-pro.linking.internal_links.min_desired` + +Specifies a minimum value for the desired number of internal links for any given entry. + +This value is used by some indicators within the Control Panel, as well as by the [Global Automatic Links](/seo-pro/global-automatic-links) feature to help make decisions. + +This configuration value is a default for Sites, and can be modified by users within the Control Panel. + +#### `statamic.seo-pro.linking.internal_links.max_desired` + +Specifies a maximum value for the desired number of internal links for any given entry. + +This value is used by some indicators within the Control Panel, as well as by the [Global Automatic Links](/seo-pro/global-automatic-links) feature to help make decisions. + +This configuration value is a default for Sites, and can be modified by users within the Control Panel. + +#### `statamic.seo-pro.linking.external_links.min_desired` + +Specifies a minimum value for the desired number of external links for any given entry. + +This value is used by some indicators within the Control Panel, as well as by the [Global Automatic Links](/seo-pro/global-automatic-links) feature to help make decisions. + +This configuration value is a default for Sites, and can be modified by users within the Control Panel. + +#### `statamic.seo-pro.linking.external_links.max_desired` + +Specifies a maximum value for the desired number of external links for any given entry. + +This value is used by some indicators within the Control Panel, as well as by the [Global Automatic Links](/seo-pro/global-automatic-links) feature to help make decisions. + +This configuration value is a default for Sites, and can be modified by users within the Control Panel. + +#### `statamic.seo-pro.linking.suggestions.result_limit` + +A hard limit on how many results should be returned when finding entries that share similar keywords or phrases. Adjust this value to find a balance between suggestions, relevancy, and performance. + +The higher the value, the more suggestions that can be returned. + +#### `statamic.seo-pro.linking.suggestions.related_entry_limit` + +A hard limit on how many results should be returned when locating similar content based on embeddings. Adjust this value to find a balance between suggestions, relevancy, and performance. + +The higher the value, the more suggestions that can be returned. + +#### `statamic.seo-pro.linking.rake.phrase_min_length` + +Specifies the minimum keyword/phrase length when generating entry keywords. + +#### `statamic.seo-pro.linking.rake.filter_numerics` + +Determines if numeric values should be filtered out of content when generating entry keywords. + +#### `statamic.seo-pro.linking.disabled_collections` + +A list of collections that should not have embeddings generated, or appear in link suggestions. \ No newline at end of file diff --git a/content/collections/seo_pro/database-schema.md b/content/collections/seo_pro/database-schema.md new file mode 100644 index 000000000..5029c2fcb --- /dev/null +++ b/content/collections/seo_pro/database-schema.md @@ -0,0 +1,138 @@ +--- +id: 8b9c92a8-5921-481a-b3ee-d80c673f08fd +blueprint: seo_pro +title: 'Database Schema' +intro: 'SEO Pro requires a database for the Content Linking features. This feature utilizes a number of tables in order to power these features.' +--- +## `seopro_entry_links` + +**Purpose**: Stores entry metadata, provides results for the Link Manager table. + +**Eloquent model**: `Statamic\SeoPro\Models\EntryLink` + +| Column | Datatype | Description | +|---|---|---| +| id | Auto-Incrementing Integer | Primary Key | +| entry_id | string | The associated entry's ID. | +| cached_title | string | The entry's title. | +| cached_uri | string | The entry's URI. | +| site | string | The associated site's handle. | +| collection | string | The associated site's handle. | +| content_hash | string | A cache key to prevent excessive content mapping regeneration. | +| analyzed_content | string | The HTML output that was analyzed to produce mappings and keywords. | +| content_mapping | JSON | A mapping of raw fieldtype data to output content. | +| external_link_count | integer | The number of detected external links. | +| internal_link_count | integer | The number of detected internal links. | +| inbound_internal_links | integer | The number of detected internal links originating from other entries. | +| external_links | JSON | A list of external link URLs. | +| internal_links | JSON | A list of internal link URLs. | +| normalized_external_links | JSON | A list of distinct external link URLs, without any fragments or identifiers. | +| normalized_internal_links | JSON | A list of distinct internal link URLs, without any fragments or identifiers. | +| ignored_entries | JSON | A list of entry IDs that will not be suggested to the entry. | +| ignored_phrases | JSON | A list of phrases or keywords that will not be suggested to the entry. | +| created_at | DateTime | The date/time the record was created. | +| updated_at | DateTime | The date/time the record was updated. | + +The `content_hash` column is derived from the associated entry's content. + +The primary way to create records in this table is by using the `Statamic\SeoPro\Linking\Links\LinkRepository::scanEntry` method. + +## `seopro_entry_keywords` + +**Purpose**: Stores generated entry keywords. + +**Eloquent model**: `Statamic\SeoPro\Models\EntryKeyword` + +| Column | Datatype | Description | +|---|---|---| +| id | Auto-Incrementing Integer | Primary Key | +| entry_id | string | The associated entry's ID. | +| site | string | The associated site's handle. | +| collection | string | The associated collection's handle. | +| blueprint | string | The associated blueprint's handle. | +| content_hash | string | A cache key to prevent re-generating entry keywords. | +| meta_keywords | JSON | Keywords extracted from the entry's URL and title. | +| content_keywords | JSON | Keywords extracted from the entry's content .| +| created_at | DateTime | The date/time the record was created. | +| updated_at | DateTime | The date/time the record was updated. | + +The `content_hash` column is derived from the associated entry's content. + +## `seopro_entry_embeddings` + + +**Purpose**: Stores generated AI embeddings for entries. + +**Eloquent model**: `Statamic\SeoPro\Models\EntryEmbedding` + +| Column | Datatype | Description | +|---|---|---| +| id | Auto-Incrementing Integer | Primary Key | +| entry_id | string | The associated entry's ID. | +| site | string | The associated site's handle. | +| collection | string | The associated collection's handle. | +| blueprint | string | The associated blueprint's handle. | +| content_hash | string | A cache key to help prevent re-generating entry embeddings. | +| configuration_hash | string | A cache key to help prevent re-generating entry embeddings. | +| embeddings | JSON | The numeric embedding array. | +| created_at | DateTime | The date/time the record was created. | +| updated_at | DateTime | The date/time the record was updated. | + +The `content_hash` column is derived from the associated entry's content. The `configuration_hash` is a hashed composite key, which includes the following information: + +* The `Statamic\SeoPro\Contracts\Content\Tokenizer` implementation used to generate embeddings +* The `statamic.seo-pro.linking.openai.token_limit` configuration value +* The `statamic.seo-pro.linking.openai.model` configuration value + +## `seopro_global_automatic_links` + +**Purpose**: Stores [Global Automatic Links](/seo-pro/global-automatic-links). + +**Eloquent model**: `Statamic\SeoPro\Models\AutomaticLink` + +| Column | Datatype | Description | +|---|---|---| +| id | Auto-Incrementing Integer | Primary Key | +| site | string | The associated site's handle. | +| is_active | bool | Indicates if the link is active. | +| link_text | string | The link's text. | +| entry_id | nullable string | The associated entry, if available. | +| link_target | string | The link's URL. | +| created_at | DateTime | The date/time the record was created. | +| updated_at | DateTime | The date/time the record was updated. | + +## `seopro_site_link_settings` + +**Purpose**: Stores custom [Content Linking site configuration](/seo-pro/site-configuration). + +**Eloquent model**: `Statamic\SeoPro\Models\SiteLinkSetting` + +| Column | Datatype | Description | +|---|---|---| +| id | Auto-Incrementing Integer | Primary Key | +| site | string | The site's handle. | +| keyword_threshold | float | The threshold to use when filtering keyword suggestions. | +| min_internal_links | int | The desired minimum number of internal links. | +| max_internal_links | int | The desired maximum number of internal links. | +| min_external_links | int | The desired maximum number of external links. | +| max_external_links | int | The desired maximum number of external links. | +| prevent_circular_links | bool | Indicates if SEO Pro should work to prevent circular link suggestions. | +| created_at | DateTime | The date/time the record was created. | +| updated_at | DateTime | The date/time the record was updated. | + +## `seopro_collection_link_settings` + +**Purpose**: Stores custom [Content Linking collection configuration](/seo-pro/collection-configuration). + +**Eloquent model**: `Statamic\SeoPro\Models\CollectionLinkSetting` + +| Column | Datatype | Description | +|---|---|---| +| id | Auto-Incrementing Integer | Primary Key | +| collection | string | The collection handle. | +| linking_enabled | bool | Indicates if Content Linking is enabled for the collection. | +| allow_linking_across_sites | bool | Indicates if entries within this collection will receive cross-site suggestions in multi-site setups. | +| allow_linking_to_all_collections | bool | Indicates if entries within this collection will receive suggestions from all other collections. If `false`, only entries from collections listed in `linkable_collections` will be used. | +| linkable_collections | JSON | A list of collections to receive entry suggestions from when `allow_linking_to_all_collections` is `false`. | +| created_at | DateTime | The date/time the record was created. | +| updated_at | DateTime | The date/time the record was updated. | \ No newline at end of file diff --git a/content/collections/seo_pro/entry-configuration.md b/content/collections/seo_pro/entry-configuration.md new file mode 100644 index 000000000..816e5bd64 --- /dev/null +++ b/content/collections/seo_pro/entry-configuration.md @@ -0,0 +1,29 @@ +--- +id: 61973878-f679-40a8-a190-08f8fec24609 +blueprint: seo_pro +title: 'Entry Configuration' +--- +SEO Pro allows you to configure a few different options on a per-entry basis. You can find entry specific configuration on an entry's individual page within the Link Manager: + +![Locating entry configuration](/img/seo-pro/entry-link-settings.png) + +## Preventing the entry from being suggested + +Sometimes you may wish to prevent certain pages from being suggested to other entries. For example, you may not want a "frequently asked questions" page to appear in suggestions since it may contain a lot of various overlapping keywords. + +Within the "Entry Settings" panel, toggle the "Can Be Suggested" option off and click "Save": + +![Excluding an entry from suggestions](/img/seo-pro/entry-link-settings.png) + +The entry will now longer be suggested to other entries. + +## Resetting entry suggestions + +To reset an entry's specific linking behavior configuration to its defaults, select "Reset Entry Suggestions" from the action drown-down menu in the top-right corner of the entry's link manager page: + +![Resetting entry suggestions](/img/seo-pro/resetting-entry-configuration.png) + +Performing this action will reset the following for the specific entry: + +* **Ignored Entries**: Any other entry's specifically ignored for the entry +* **Ignored Phrases**: Any phrases or keywords that are ignored for the entry \ No newline at end of file diff --git a/content/collections/seo_pro/file-usage.md b/content/collections/seo_pro/file-usage.md new file mode 100644 index 000000000..3a20791ac --- /dev/null +++ b/content/collections/seo_pro/file-usage.md @@ -0,0 +1,31 @@ +--- +id: d887ae27-c075-42fa-9925-bb89d58015cd +blueprint: seo_pro +title: 'File Usage' +--- +For advanced devs, you may bypass the CP and configure your SEO settings through files. There are 3 sorts of values you may save. + +## Custom Hardcoded Strings + +```yaml +title: "A hardcoded string" +``` + +## Field References + +Prefix a field name with `@seo:` to have that field's value referenced automatically. + +A field in a specific fieldset may be specified (this is how the CP will save them). The fieldset is completely optional and currently provides no additional benefit. + +```yaml +title: "@seo:title" +title: "@seo:post/title" # with optional fieldset +``` + +## Antlers Templating + +You may use Statamic Antlers templating in your strings. When doing this, the addon will not apply any automatic parsing rules (limiting the length of the description, for example). + +```yaml +description: "{{ content | strip_tags | truncate(250, '...') }}" +``` \ No newline at end of file diff --git a/content/collections/seo_pro/general-usage.md b/content/collections/seo_pro/general-usage.md new file mode 100644 index 000000000..c79d22245 --- /dev/null +++ b/content/collections/seo_pro/general-usage.md @@ -0,0 +1,48 @@ +--- +id: 7f8eca1b-8d0c-4daf-99ce-b23a2ac44256 +blueprint: seo_pro +title: 'General Usage' +--- +SEO settings will cascade down from the global defaults, to the collection/taxonomy level, and finally to the entry/term level. + +Empty meta tags will not be rendered, which allows you to optionally set your own tags with other means if you so choose. + +## Site Defaults + +Head to `Tools > SEO Pro > Site Defaults` and configure your default settings. The defaults will be used if you haven't set anything more specific at the collection or entry level. + +> Values configured here will be saved into `content/seo.yaml`. + +You may choose to pull data from other fields, enter hardcoded strings, or use Antlers templating. See [File Usage](/seo-pro/file-usage) for more details. + +## Section Defaults + +Each section may be configured independently at the Collection / Taxonomy level. Head to `Tools > SEO Pro > Section Defaults` to configure default settings at this level. You may opt to inherit values from the defaults and tweak as necessary. + +> Values configured here will be saved into the seo array (within `inject`) in the respective section's yaml config. + +You may disable a section by toggling the "Enabled" field when editing a section, or set `seo: false` within the inject array in that section's yaml config. Disabling a section will prevent it's items from being included in reports, the sitemap, and prevent the template tag from rendering anything. + +## Entries and Terms + +It's better to configure your collections and taxonomies to dynamically pull from fields. However, an SEO tab will be added to each item's publish page and you are free to override any values there. + +> Values configured here will be saved into the `seo` array in the item's front-matter. + +## Assets + +If you wish to use assets in your meta, you can [publish the SEO Pro config](/seo-pro/advanced-configuration) and specify an asset container, as well as the glide preset to be used. + +> You may disable the glide preset altogether by setting `'open_graph_preset' => false,` in your config. + +## Custom Statamic Routes + +In the case that you're loading a custom [Statamic Route](/routing#statamic-routes), you can pass SEO meta directly into the route data param. This allows you to define custom meta on a route-by-route basis in situations without a proper collection entry. + +```php +Route::statamic('search', 'search/index', [ + 'title' => 'Search', + 'description' => 'Comprehensive Site Search.', + // ... +]); +``` \ No newline at end of file diff --git a/content/collections/seo_pro/global-automatic-links.md b/content/collections/seo_pro/global-automatic-links.md new file mode 100644 index 000000000..53daed189 --- /dev/null +++ b/content/collections/seo_pro/global-automatic-links.md @@ -0,0 +1,68 @@ +--- +id: 46995713-fcbc-48ce-b579-e9c950241597 +blueprint: seo_pro +title: 'Global Automatic Links' +intro: |- + Global Automatic Links are an opt-in feature that allows SEO Pro to automatically insert links into your site's rendered output. + + ![Locating Global Automatic Links](/img/seo-pro/locating-automatic-links.png) +--- +## Managing Automatic Links + +Global Automatic Links may be created and managed within the Global Automatic Links dashboard. + +![Global Automatic Link Fields](/img/seo-pro/global-automatic-link-fields.png) + +Each global link contains the following information: + +* **Link Text**: The text or phrase that will be used to match and insert the link. +* **Link Target**: The URL the link should direct visitors to. +* **Active Link**: Indicates if a link is currently active. Changing this setting will take effect on new, uncached, requests. Changing this setting *does not* currently invalidate existing pages. + +## Updating site templates + +To have SEO Pro automatically insert links into your site's output, you need to update your template's to use the `seo_pro:content` tag pair, with the `auto_link` parameter set to `true`: + +::tabs + +::tab antlers +```antlers +{{ seo_pro:content auto_link="true" }} + +{{ /seo_pro:content }} +``` +::tab blade +```blade + + + +``` +:: + +With that template change, whenever SEO Pro finds matching text within your site's output, it will work to automatically insert the corresponding link. + +## How automatic links are inserted + +SEO Pro will use the Global Automatic Link's "Link Text" value to search for matching text within the site's output; only matching text will be converted to a link. + +SEO Pro will *not* create a link under the following conditions, even if the output contains matching text: + +* The output already contains a link to the automatic link's URL, +* The number of existing links already exceeds the [site's max internal or external link count](/seo-pro/site-configuration#available-configuration-options) +* The matching text was found in an existing link, header, code block, or other ineligible element + +## Customizing automatic links + +SEO Pro uses templates when creating the link to insert into your site's output. You can override these templates by publishing the associated views with the following command: + +```bash + php artisan vendor:publish --tag=seo-pro-linking-views +``` + +After the command runs, you will find three new templates within your resources directory: + +* `vendor/seo-pro/links/automatic.antlers.html`: Determines the format of Global Automatic Links +* `vendor/seo-pro/links/html.antlers.html`: Determines the format of links inserted into [Text](/fieldtypes/text) and [Textarea](/fieldtypes/textarea) fieldtypes +* `vendor/seo-pro/links/markdown.antlers.html`: Determines the format of links inserted into the [Markdown](/fieldtypes/markdown) fieldtypes + +To customize Global Automatc Links, you may edit the `vendor/seo-pro/links/automatic.antlers.html` template. \ No newline at end of file diff --git a/content/collections/seo_pro/graphql.md b/content/collections/seo_pro/graphql.md new file mode 100644 index 000000000..a6ca580ac --- /dev/null +++ b/content/collections/seo_pro/graphql.md @@ -0,0 +1,63 @@ +--- +id: 3178acd7-e001-421c-8011-291e8989e6dd +blueprint: seo_pro +title: GraphQL +--- +If you're accessing content through Statamic's [GraphQL API](/graphql), you can render SEO meta on your entries and terms this way as well. For example, in an [entries query](/graphql#entries-query) you can access prerendered SEO meta `html` under `seo`: + +```graphql +{ + entries { + data { + seo { + html + } + } + } +} +``` + +Or if you prefer to render your own SEO meta HTML by hand, you can access the SEO Cascade directly (which will respect your [Site Defaults](site-defaults) and [Section Defaults](#section-defaults)): + +```graphql +{ + entries { + data { + seo { + site_name + site_name_position + site_name_separator + title + compiled_title + description + priority + change_frequency + og_title + canonical_url + alternate_locales { + url + site { + handle + locale + } + } + prev_url + next_url + home_url + humans_txt + twitter_card + twitter_handle + image { + url + permalink + } + last_modified(format: "Y-m-d") + } + } + } +} +``` + +:::tip +Feel free to browse the schema and test output through the GraphiQL explorer in your CP at `/cp/graphiql`. +::: \ No newline at end of file diff --git a/content/collections/seo_pro/installing-seo-pro.md b/content/collections/seo_pro/installing-seo-pro.md new file mode 100644 index 000000000..5b3f96ece --- /dev/null +++ b/content/collections/seo_pro/installing-seo-pro.md @@ -0,0 +1,27 @@ +--- +id: 40678d50-2bca-427d-8c9a-5c1bd4cbe1f5 +blueprint: seo_pro +title: 'Installing & Uninstalling' +--- +## Installation + +1. Install SEO Pro with Composer: + +```bash +composer require statamic/seo-pro +``` + +2. Add the Antlers tag or Blade directive somewhere between your `` tags. + +* Antlers: `{{ seo_pro:meta }}` +* Blade `@seo_pro('meta')` + +## Uninstalling + +To uninstall, run: + +```bash +composer remove statamic/seo-pro +``` + +If you've saved any blueprints while SEO Pro was installed, an `seo` field will have been added to them. You will need to manually remove the `seo` field from the corresponding blueprints. \ No newline at end of file diff --git a/content/collections/seo_pro/link-replacers.md b/content/collections/seo_pro/link-replacers.md new file mode 100644 index 000000000..bbfe848bd --- /dev/null +++ b/content/collections/seo_pro/link-replacers.md @@ -0,0 +1,87 @@ +--- +id: 9bad1545-eeaa-4376-b132-61e26abd9062 +blueprint: seo_pro +title: 'Link Replacers' +--- +Link replacers are closely related to content mapping, and perform the actual link insertion when users [accept link suggestions](/seo-pro/the-link-manager#accepting-link-suggestions) within the [Link Manager](/seo-pro/the-link-manager). + +Content mapping provides the path to the individual fields and their content, and link replacers are responsible for performing the update on the field's raw value. For example, the Bard link replacer works to decorate existing content nodes with the appropriate `href` marks, while the Textarea link replacer might do a simpler string replace. + +SEO Pro ships with default replacer implementations for the following fieldtypes: + +* [Bard](/fieldtypes/bard) +* [Markdown](/fieldtypes/markdown) +* [Textarea](/fieldtypes/textarea) +* [Text](/fieldtypes/text) + +## Custom replacer implementations + +Custom link replacer implementations must implement the `Statamic\SeoPro\Contracts\Linking\Links\FieldtypeLinkReplacer` interface. + +As an example, the default `TextReplacer` implementation is as follows: + +```php +field->getValue(), + $context->replacement->phrase + ); + } + + public function replace(ReplacementContext $context): bool + { + $html = Str::replaceFirst( + $context->replacement->phrase, + $context->render('html'), + $context->field->getValue() + ); + + $context->field->update($html)->save(); + + return true; + } +} + +``` + +The static `fieldtype` method lets SEO Pro know the handle of the fieldtype the replacer is intended for. + +The `canReplace` method returns a value indicating if the SEO Pro will be able to insert the link, while the `replace` method performs the actual content update. + +## Registering custom replacers + +Each custom replacer implementation needs to be registered. You may register them with the shared `LinkReplacer` instance: + +```php +use Statamic\SeoPro\Content\LinkReplacer; + +class MyServiceProvider extends ServiceProvider +{ + public function boot() + { + /** @var \LinkReplacer $replacer */ + $replacer = $this->app->make(LinkReplacer::class); + + $replacer->registerReplacers([ + CustomReplacer::class, + ]); + } +} +``` \ No newline at end of file diff --git a/content/collections/seo_pro/parsing-content-and-content-mapping.md b/content/collections/seo_pro/parsing-content-and-content-mapping.md new file mode 100644 index 000000000..a439c7057 --- /dev/null +++ b/content/collections/seo_pro/parsing-content-and-content-mapping.md @@ -0,0 +1,144 @@ +--- +id: 14e56924-d3e2-4912-9a53-8e19cbad063e +blueprint: seo_pro +title: 'Parsing Content & Content Mapping' +intro: "SEO Pro's content linking features need to parse and analyze the content of pages within a site. SEO Pro does this making web requests to each eligible entry and parsing the HTML response." +--- +## Template updates + +For most sites, you will not need to do anything special since SEO Pro will locate and parse the contents of any `
` tags within the web response. + +If a page does not contain these tags, if you would like to have more control over the content parsed by SEO Pro, you may use the `seo_pro:content` tag pair: + +::tabs + +::tab antlers +```antlers +{{ seo_pro:content }} + +{{ /seo_pro:content }} +``` +::tab blade +```blade + + + +``` +:: + +The `seo_pro:content` tag pair may be used multiple times. This tag works be emitting special HTML comments, but only when SEO Pro is scanning your site for content and links. + +Once SEO Pro has retrieved the content for an entry, it will then perform a "content mapping" step, which works to associate the raw field data with the rendered output. This information is then used when producing link suggestions or updating entry contents. + +## Content mapping + +Content mapping is a sub-feature that maps the raw fieldtype data of your site's entries to their content and final HTML output. This allows SEO Pro to update and insert links within your content automatically. + +### How content mapping works + +Content mapping works by iterating all of the fields within an entry and building up an internal path to the field's final contents. Because of this, each fieldtype needs its own content mapper in order to work correctly with SEO Pro's linking features. SEO Pro ships with support for the following content mappers by default: + +* [Bard](/fieldtypes/bard) +* [Grid](/fieldtypes/grid) +* [Group](/fieldtypes/group) +* [Markdown](/fieldtypes/markdown) +* [Replicator](/fieldtypes/replicator) +* [Text](/fieldtypes/text) +* [Textarea](/fieldtypes/textarea) + +The results of the content mapping process are stored within the `seopro_entry_links.content_mapping` database column. This column contains a key/value pair of all discovered content paths and their raw content. + +The structure of a simple content path, such as one for a text fieldtype, is as follows: + +```json +{ + "title{display_name:Title}{type:text}": + "I had a really good idea but got sidetracked and forgot it." +} +``` + +The key of the content mapping provides information that allows SEO Pro to relocate the content within your entry's content, as well as additional metadata, such as the field's handle, type, and display name. The field handle will always appear first, with additional information appearing after it within curly braces. + +A more complicated content path for a nested grid might look like this: + +```json +{ + "options{display_name:Options}{type:grid}[4]type{type:text}": + "string", + "options{display_name:Options}{type:grid}[4]description{type:markdown}": + "Sort and order by field name. E.g. `'title:desc'`. Defaults to the collection's settings." +} +``` + +This content path also includes square brackets, which will be common in fieldtypes like Bard, Replicator, or the Grid, which may allow for multiple array values. + +### Implementing custom content mappers + +All content mappers must implement the `Statamic\SeoPro\Contracts\Content\FieldtypeContentMapper` interface. The simplest way to do this is to extend SEO Pro's `Statamic\SeoPro\Content\Mappers\AbstractFieldMapper` and implement the required methods: + +```php +app->make(ContentMapper::class); + + $contentMapper->registerMappers([ + CustomMapper::class, + ]); + } +} +``` + +### Disabling content mapping for a field + +To disable content mapping for a specific field, you may add the `seo_pro_map_content` key to the field's configuration within the blueprint and set it to `false`: + +```yaml +title: Blog +sections: + main: + display: Main + fields: + - + handle: title + field: + type: text + required: true + validate: + - required + - + handle: intro + field: + type: bard + display: Intro + seo_pro_map_content: false +``` \ No newline at end of file diff --git a/content/collections/seo_pro/reports.md b/content/collections/seo_pro/reports.md new file mode 100644 index 000000000..b800193f7 --- /dev/null +++ b/content/collections/seo_pro/reports.md @@ -0,0 +1,29 @@ +--- +id: 700c8a78-0878-455d-b7d3-6cc2660c4d0a +blueprint: seo_pro +title: Reports +--- +You may generate an SEO report that checks all the pages of your site against a number of tests. The tests include mandatory items like title tag uniqueness, or suggested items like URLs being no more than 3 segments. Failing a mandatory item will result in a fail where failing a suggested item will result in a warning. + +Reports will stick around until deleted, so you are free to compare reports to see how you are progressing. + +You may generate a report through the Control Panel, or by running `php please seo-pro:generate-report`. + +## Queuing Reports + +Depending on the size of your site, generating a report may take a while. To prevent request timeouts, you may enable queues, and the reports will be truly queued in the background. + +> A popular choice is to use a [Redis](https://laravel.com/docs/11.x/redis) store and [queue driver](https://laravel.com/docs/11.x/queues#driver-prerequisites), along with [Laravel Horizon](https://laravel.com/docs/11.x/queues#driver-prerequisites) for managing your Redis queues. + +## Widget + +You may add a reports widget to your dashboard to get a quick insight into your site's SEO status. Add the following to your `widgets` array within `config/statamic/cp.php` to show the latest report's score: + +```php +'widgets' => [ + [ + 'type' => 'seo_pro', + 'width' => 50, + ] +], +``` \ No newline at end of file diff --git a/content/collections/seo_pro/site-configuration.md b/content/collections/seo_pro/site-configuration.md new file mode 100644 index 000000000..a44c4ac01 --- /dev/null +++ b/content/collections/seo_pro/site-configuration.md @@ -0,0 +1,36 @@ +--- +id: c9fd20d2-ab81-44d7-95bc-dff9068daa06 +blueprint: seo_pro +title: 'Site Configuration' +--- +Site configuration changes may be done within Control Panel. To update a site's content linking configuration, users may navigate to the "Site Linking Behavior" page within the SEO Pro Link Manager: + +![Site configuration](/img/seo-pro/site-config-link-manager.png) + +## Available configuration options + +All available sites will appear on the Site Linking Behavior page. To modify the available site settings, select "Edit Linking Behavior" from the actions menu to the right of the site to update. Once selected, the "Site Linking Behavior" panel will appear: + +![Available site options](/img/seo-pro/available-site-options.png) + + +By default, sites will inherit the following values from the `config/statamic/seo-pro.php` configuration file *until* they are changed within the Control Panel: + +* **Keyword Threshold**: A threshold value that can be used to filter suggested entries based on how relevant keyword and phrases are to the target entry. A lower value will result in *more* suggestions, but may reduce the quality of the matches. Lower ranked matches may also not be able to be linked automatically as they may not share exact keyword or phrase matches. +* **Prevent Circular Link Suggestions**: When enabled, SEO Pro will attempt to prevent circular link suggestions. A circular link suggestion would occur if Entry B is suggested for Entry A, but Entry B already links to Entry A. +* **Min. Internal Links**: Specifies a minimum value for the desired number of internal links for any given entry. This value is used by some indicators within the Control Panel, as well as by the [Global Automatic Links](/seo-pro/global-automatic-links) feature to help make decisions. +* **Max Internal Links**: Specifies a maximum value for the desired number of internal links for any given entry. This value is used by some indicators within the Control Panel, as well as by the [Global Automatic Links](/seo-pro/global-automatic-links) feature to help make decisions. +* **Min External Links**: Specifies a minimum value for the desired number of external links for any given entry. This value is used by some indicators within the Control Panel, as well as by the [Global Automatic Links](/seo-pro/global-automatic-links) feature to help make decisions. +* **Max External Links**: Specifies a maximum value for the desired number of external links for any given entry. This value is used by some indicators within the Control Panel, as well as by the [Global Automatic Links](/seo-pro/global-automatic-links) feature to help make decisions. + +The following configuration values are managed *per-site*: + +* **Ignored Phrases**: A list of keywords or phrases that will be added to the "stop word" list when extracting keywords and phrases from entries. + +Site configuration is stored within the `seopro_site_link_settings` database table. + +## Resetting site configuration + +To reset a sites's configuration to its default values, select the "Reset Site Settings" from the actions drop-down menu to the right of the desired site: + +![Resetting site settings](/img/seo-pro/resetting-site-configuration.png) \ No newline at end of file diff --git a/content/collections/seo_pro/the-link-manager.md b/content/collections/seo_pro/the-link-manager.md new file mode 100644 index 000000000..6006b8777 --- /dev/null +++ b/content/collections/seo_pro/the-link-manager.md @@ -0,0 +1,117 @@ +--- +id: 17817a33-c361-48dc-90a1-a6ec3f1ec0d9 +blueprint: seo_pro +title: 'The Link Manager' +--- +The Link Manager provides a birds-eye view of all crawled entries, displaying their internal, external and inbound internal link counts: + +![The Link Manager](/img/seo-pro/link-manager.png) + +Clicking an entry's title will navigate to the entry's link management page, which contains the following information: + +* **Link Suggestions**: Provides a listing of all link suggestions, with options to accept or reject the suggestion, or edit the entry, depending on the current user's permissions. +* **Related Content**: Provides a listing of all related content, determined by comparing their embeddings. The entries listed here are ultimately what are used to produce the suggestions on the "Link Suggestions" page. +* **Internal Links**: Provides a listing of all internal links present in the current entry's content. +* **External Links**: Provides a listing of all external links present in the current entry's content. +* **Inbound Internal Links**: Provides a listing of all entries that link to the current entry. + +:::tip +Reporting metrics, such as the internal and external link counts, are only calculated using the information available within the `seopro_entry_links` database table. Ingesting data from external providers is not available at this time. +::: + +## Link Suggestions + +The Link Suggestions page provides a list of suggested internal links for the current entry: + +![Link Suggestions](/img/seo-pro/link-suggestions.png) + +Each link suggestion will contain the following: + +* **Link Text**: The phrase from the current entry that is relevant to the keywords or phrases in common with the suggested entry. Any text highlighted in blue is the specific keyword or phrase that was common between the current entry and the suggested entry. +* **Can Auto Apply**: When true (or checked), this indicates that the suggestion engine may be able to insert the link within the content automatically, without needing manual content edits. +* **Relevancy Score**: A relative score to help gauge how relevant the suggested link is to the current entry. There is no specific range for this score, and the scores should be weighed relative to other suggestion's scores; higher is generally "more relevant". +* **Link Target**: The URI of the suggested entry. +* Suggestion Actions: Contains additional actions that can be taken for the suggestion. The following default actions are available, depending on user permissions: + * **Edit Entry**: Navigates the user to the edit entry page for the current entry. + * **Accept Suggestion**: Reveals an interactive link creation panel to accept the current link suggestion. Users may also click the "Link Text" column when "Can Auto Apply" is true. + * **Ignore Suggestion**: Provides options to ignore the link suggestion. + +### Accepting Link Suggestions + +Clicking on the Link Text column, or selecting "Accept Suggestion" action within the suggestion's action drop-down menu will reveal the Link Suggestion editor: + +![Link Suggestion Editor](/img/seo-pro/link-suggestion-url.png) + +There are three main sections of the Link Suggestion editor: + +1. **Field to Update**: The specific field that SEO Pro will update if the suggestion is saved. +2. **Link Target**: The URI (or entry) of the link to insert. +3. **Link Text**: Provides an interactive way to select which text from the entry should be converted to a link. + +When adjusting the Link Text, a you may mouse over additional words. The editor will highlight the additional words; clicking the word under the mouse cursor will select all highlighted words add they will become part of the inserted link's text: + +![Selecting more words for the link text](/img/seo-pro/link-editor-hover.png) + +Hovering over words that are already part of the link's text will highlight all words up to the current word *red*. Clicking this word under the cursor will remove those words from the inserted link's text: + +![Removing words from the link text](/img/seo-pro/hover-delete-text.png) + +You can hold the "shift" key to invert the selection. This is useful to remove words from the beginning of the active phrase: + +![Removing words from the beginning of the link text](/img/seo-pro/inverted-selection-shift.png) + +Once you are happy with the link target and text, you may click "Save" to have SEO Pro automatically insert the link into the entry's content. + +:::tip +There are times when SEO Pro will be unable to automatically insert the desired link into the current entry. This can be caused for a variety of reasons, most of which are attempting to insert into an existing link, or there is punctuation SEO Pro doesn't know how to safely manage. In these scenarios, you will be asked if you'd like to manually update the entry instead. +::: + +### Ignoring Link Suggestions + +Not all suggestions will be useful. Sometimes this is because a user might not want to link to that specific entry, or they would like a certain phrase or keyword to *not* be suggested. To ignore a suggestion, the you may select the "Ignore Suggestion" from the actions drop-down menu next to the suggestion: + +![Ignore suggestion action](/img/seo-pro/ignore-suggestion-action.png) + +Once the "Ignore Suggestion" action has been selected, the "Ignore Suggestion" modal will appear with multiple options: + +![Ignore suggestion modal](/img/seo-pro/ignore-suggestion-modal.png) + +When ignoring a suggestion, you will be prompted to select an action and a scope. These two options work together to make fine-tune future link suggestions. + +Available actions and scopes: + +* **Do not suggest this entry**: Prevents the entry from being suggested again + * **Scope: "This entry"**: Prevents the entry from being suggested again for the currently active entry, but allows it to be suggested to *other* entries. + * **Scope: "All entries in this site"**: Selecting this scope will update the suggested entries "Can Be Suggested" setting, preventing it from being suggested to *any* other entry. +* **Do not suggest this phrase**: + * **Scope: "This entry"**: Prevents the suggested phrase from being suggested again to the current entry. + * **Scope: "All entries in this site"**: Adds the suggested phrase to the site's "Ignored Phrases" list. + +## Related Content + +The "Related Content" page lists other entries that are determined to be related, based on each entry's embeddings. Eligible entries are determined based on the collection's linking settings, as well as the current entries. The maximum number of results returned is governed by the `statamic.seo-pro.linking.suggestions.related_entry_limit` configuration value. + +![Related Content Page](/img/seo-pro/related-content-page.png) + +This table largely serves as a way to see which entries will be used to create link suggestions on the "Link Suggestions" page. + +### Ignoring Related Content + +Similar to ignoring link suggestions, you may ignored related content. To ignore related content, users may select the "Not Related" option from the actions drop-down menu next to the listed entry. + +Once the "Not Related" option has been selected, the "Ignore Related Content" modal will appear with additional options: + +![Ignore Related Content Modal](/img/seo-pro/ignore-related-content-modal.png) + +Users may select one of the following scopes when ignoring an entry: + +* **This entry**: Prevents the entry from being suggested again for the currently active entry, but allows it to be suggested to *other* entries. +* **All entries in this site**: Selecting this scope will update the suggested entries "Can Be Suggested" setting, preventing it from being suggested to *any* other entry. + +## Internal and External Link Reports + +These pages provide a simple list of internal or external links discovered within entry contents. + +## Inbound Internal Links + +This page provides a list of other entries that link to the current entry. \ No newline at end of file diff --git a/content/navigation/seo_pro.yaml b/content/navigation/seo_pro.yaml new file mode 100644 index 000000000..5a4a207c3 --- /dev/null +++ b/content/navigation/seo_pro.yaml @@ -0,0 +1,3 @@ +title: 'SEO Pro' +collections: + - seo_pro diff --git a/content/trees/navigation/seo_pro.yaml b/content/trees/navigation/seo_pro.yaml new file mode 100644 index 000000000..6d96a3e90 --- /dev/null +++ b/content/trees/navigation/seo_pro.yaml @@ -0,0 +1,70 @@ +tree: + - + id: bbfe687d-716f-4691-a010-d7be31b38228 + title: 'Getting Started' + children: + - + id: 0387ab25-0d52-4303-9ff7-5de71d99ce05 + entry: 40678d50-2bca-427d-8c9a-5c1bd4cbe1f5 + - + id: aaebf736-b30f-40b9-95b1-e53b329bd880 + entry: 7f8eca1b-8d0c-4daf-99ce-b23a2ac44256 + - + id: 63eb6b1a-5706-46e7-9655-4e25c405966c + entry: d887ae27-c075-42fa-9925-bb89d58015cd + - + id: 20df4b85-578f-4eb9-9232-82f3d4719c27 + entry: 700c8a78-0878-455d-b7d3-6cc2660c4d0a + - + id: ab0840f8-060d-496c-94a1-a425e38d4d49 + entry: 1917f8a1-4b89-410b-ba3d-20d85bcc9ded + - + id: 422372b4-8755-4a1e-9ac2-44b43178062f + entry: 3178acd7-e001-421c-8011-291e8989e6dd + - + id: 5044fe18-2116-4a3f-9359-ec58df47fb26 + title: 'Content Linking' + children: + - + id: cb05b1b9-f7b3-4cdf-acea-6f3c79e189a0 + entry: 44e6499b-8232-47ff-8853-3d2b2c82e565 + - + id: b761449a-53aa-4c42-8577-6d41f24e8075 + entry: 17817a33-c361-48dc-90a1-a6ec3f1ec0d9 + - + id: e4d40367-6167-428d-a39a-6e30f1fbe6b4 + entry: c9fd20d2-ab81-44d7-95bc-dff9068daa06 + - + id: eb886aa5-bc24-4538-b5fe-dfe2118c1cda + entry: 245b78ff-2860-473d-a700-8c1eaed30d39 + - + id: 643edcdf-32f3-4f3c-9ad6-6923c26a65b0 + entry: 61973878-f679-40a8-a190-08f8fec24609 + - + id: 613a33f5-eb41-41b7-b728-0fbf3ad7735b + entry: 46995713-fcbc-48ce-b579-e9c950241597 + - + id: bb7ed9a3-6d63-4f5c-8a15-d84d4990d30d + entry: 33fbfb5a-5f44-4a58-a923-ab1f4ea310a0 + - + id: 94934127-c88c-424b-8ae6-8e0f6a9e20b5 + entry: 2efde67f-35b2-40fd-b54b-b53fb84e7c3a + - + id: 1dac7054-fc0c-4df6-aa03-9e7ff22df139 + entry: c7b6e4b0-15ac-4301-842f-61cabf36c1e7 + - + id: 295adf15-4363-452a-8587-f4f722e9c904 + title: Extending + children: + - + id: 0907a628-279f-4dd9-bb55-4d5a70116807 + entry: 14e56924-d3e2-4912-9a53-8e19cbad063e + - + id: 4d6853e1-a5fe-417f-9a39-03742304d4d7 + entry: 9bad1545-eeaa-4376-b132-61e26abd9062 + - + id: 837d6e22-eb0d-4877-a736-6a86f48eb652 + entry: 41cd0830-04f4-43bd-875e-46f0d608be9e + - + id: 17c32f95-0470-45bb-83d7-577641044dc2 + entry: 8b9c92a8-5921-481a-b3ee-d80c673f08fd diff --git a/public/img/seo-pro/.gitkeep b/public/img/seo-pro/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/public/img/seo-pro/available-site-options.png b/public/img/seo-pro/available-site-options.png new file mode 100644 index 000000000..85a087792 Binary files /dev/null and b/public/img/seo-pro/available-site-options.png differ diff --git a/public/img/seo-pro/collection-allow-linking-toggle.png b/public/img/seo-pro/collection-allow-linking-toggle.png new file mode 100644 index 000000000..f5a8707f0 Binary files /dev/null and b/public/img/seo-pro/collection-allow-linking-toggle.png differ diff --git a/public/img/seo-pro/collection-linking-behavior.png b/public/img/seo-pro/collection-linking-behavior.png new file mode 100644 index 000000000..2101501eb Binary files /dev/null and b/public/img/seo-pro/collection-linking-behavior.png differ diff --git a/public/img/seo-pro/edit-collection-linking-behavior.png b/public/img/seo-pro/edit-collection-linking-behavior.png new file mode 100644 index 000000000..891cf4818 Binary files /dev/null and b/public/img/seo-pro/edit-collection-linking-behavior.png differ diff --git a/public/img/seo-pro/entry-link-settings.png b/public/img/seo-pro/entry-link-settings.png new file mode 100644 index 000000000..d7f0c2edb Binary files /dev/null and b/public/img/seo-pro/entry-link-settings.png differ diff --git a/public/img/seo-pro/global-automatic-link-fields.png b/public/img/seo-pro/global-automatic-link-fields.png new file mode 100644 index 000000000..a1abcca48 Binary files /dev/null and b/public/img/seo-pro/global-automatic-link-fields.png differ diff --git a/public/img/seo-pro/hover-delete-text.png b/public/img/seo-pro/hover-delete-text.png new file mode 100644 index 000000000..0cec0c138 Binary files /dev/null and b/public/img/seo-pro/hover-delete-text.png differ diff --git a/public/img/seo-pro/ignore-related-content-modal.png b/public/img/seo-pro/ignore-related-content-modal.png new file mode 100644 index 000000000..310ac3ae9 Binary files /dev/null and b/public/img/seo-pro/ignore-related-content-modal.png differ diff --git a/public/img/seo-pro/ignore-suggestion-action.png b/public/img/seo-pro/ignore-suggestion-action.png new file mode 100644 index 000000000..143e089db Binary files /dev/null and b/public/img/seo-pro/ignore-suggestion-action.png differ diff --git a/public/img/seo-pro/ignore-suggestion-modal.png b/public/img/seo-pro/ignore-suggestion-modal.png new file mode 100644 index 000000000..404ccf82c Binary files /dev/null and b/public/img/seo-pro/ignore-suggestion-modal.png differ diff --git a/public/img/seo-pro/inverted-selection-shift.png b/public/img/seo-pro/inverted-selection-shift.png new file mode 100644 index 000000000..5410a4ce2 Binary files /dev/null and b/public/img/seo-pro/inverted-selection-shift.png differ diff --git a/public/img/seo-pro/link-editor-hover.png b/public/img/seo-pro/link-editor-hover.png new file mode 100644 index 000000000..f308ef154 Binary files /dev/null and b/public/img/seo-pro/link-editor-hover.png differ diff --git a/public/img/seo-pro/link-manager.png b/public/img/seo-pro/link-manager.png new file mode 100644 index 000000000..c414f6979 Binary files /dev/null and b/public/img/seo-pro/link-manager.png differ diff --git a/public/img/seo-pro/link-suggestion-url.png b/public/img/seo-pro/link-suggestion-url.png new file mode 100644 index 000000000..6eea46b81 Binary files /dev/null and b/public/img/seo-pro/link-suggestion-url.png differ diff --git a/public/img/seo-pro/link-suggestions.png b/public/img/seo-pro/link-suggestions.png new file mode 100644 index 000000000..f7c90e80b Binary files /dev/null and b/public/img/seo-pro/link-suggestions.png differ diff --git a/public/img/seo-pro/locating-automatic-links.png b/public/img/seo-pro/locating-automatic-links.png new file mode 100644 index 000000000..e6eff7d4a Binary files /dev/null and b/public/img/seo-pro/locating-automatic-links.png differ diff --git a/public/img/seo-pro/related-content-page.png b/public/img/seo-pro/related-content-page.png new file mode 100644 index 000000000..6c5032514 Binary files /dev/null and b/public/img/seo-pro/related-content-page.png differ diff --git a/public/img/seo-pro/resetting-collection-settings.png b/public/img/seo-pro/resetting-collection-settings.png new file mode 100644 index 000000000..e00d348fc Binary files /dev/null and b/public/img/seo-pro/resetting-collection-settings.png differ diff --git a/public/img/seo-pro/resetting-entry-configuration.png b/public/img/seo-pro/resetting-entry-configuration.png new file mode 100644 index 000000000..37ef49fb4 Binary files /dev/null and b/public/img/seo-pro/resetting-entry-configuration.png differ diff --git a/public/img/seo-pro/resetting-site-configuration.png b/public/img/seo-pro/resetting-site-configuration.png new file mode 100644 index 000000000..b551c11e0 Binary files /dev/null and b/public/img/seo-pro/resetting-site-configuration.png differ diff --git a/public/img/seo-pro/restricting-collection-suggestions.png b/public/img/seo-pro/restricting-collection-suggestions.png new file mode 100644 index 000000000..3d1c6fe5c Binary files /dev/null and b/public/img/seo-pro/restricting-collection-suggestions.png differ diff --git a/public/img/seo-pro/site-config-link-manager.png b/public/img/seo-pro/site-config-link-manager.png new file mode 100644 index 000000000..6646a51a4 Binary files /dev/null and b/public/img/seo-pro/site-config-link-manager.png differ diff --git a/resources/blueprints/collections/seo_pro/seo_pro.yaml b/resources/blueprints/collections/seo_pro/seo_pro.yaml new file mode 100644 index 000000000..abcf30c18 --- /dev/null +++ b/resources/blueprints/collections/seo_pro/seo_pro.yaml @@ -0,0 +1,27 @@ +tabs: + main: + display: Main + sections: + - + fields: + - + handle: title + field: + type: text + required: true + validate: + - required + - + import: page + sidebar: + display: Sidebar + sections: + - + fields: + - + handle: slug + field: + type: slug + localizable: true + validate: 'max:200' +title: 'SEO Pro' diff --git a/resources/views/seo-pro/index.antlers.html b/resources/views/seo-pro/index.antlers.html new file mode 100644 index 000000000..e075ab91c --- /dev/null +++ b/resources/views/seo-pro/index.antlers.html @@ -0,0 +1,15 @@ +{{ partial:header }} + +
+ {{ content | toc:ids }} + + {{ if options }} +

Options

+ {{ if options_content }} + {{ options_content | markdown }} + {{ /if }} + {{ partial:details :details="options" }} + {{ /if }} +
+ +{{ partial:edit }}